Building a small form factor pfSense Router

About a year ago, I was looking around at building a pfSense server to replace my Netgear Nighthawk as I was bringing in some new hardware and wanted to create a couple of VLANs and setup some more advanced routing and such.

I did some research and stumbled across the apu1c4 which seemed like it would be perfect for my needs. pcengines also carries all the other components including case to build a very small device with a lot of power. Note that the below guide walks through the setup specifically with a machine running OS X 10.10. Instructions for writing the pfSense image for other Operating Systems can be found here while instructions for consoling to the device from other Operating Systems can be found here.


Here is a list of all the components you would need to order:

  • One of either the apu1d (2GB of memory) or the apu1d4 (4GB of memory)
  • One power adapter (Note that PC Engines makes adapters for the EU and UK
  • One case (They do have other colors available however black is recommended by manufacturer for heat reasons)
  • One mSata SSD Drive (16GB) or larger depending on your use case (storing large amounts of logs for example)
  • One Null Modem cable
  • One USB to Serial cable (this one I know works with OS X)

Building the server

  1. Follow this guide here which shows how to install the head spreader and insert the board into the bottom of the case. (Ensure you first remove the hex screws on the serial port)
  2. Install the mSATA drive by inserting it into the socket on the board.
  3. Close the case, and screw the hex nuts back in.


  1. Download a copy of pfSense here. When prompted choose AMD64 for the Architecture, Live CD with Installer (on USB Memstick) for the Platform, and Serial for the console.
  2. Using diskutil list find your usb device. In the below example, I have inserted a 16GB USB drive.
  3. In the above output, you can see that my usb drive is at /dev/disk4. We need to unmount disk4s1 however before we can write to the device.
  4. We can now dd our pfSense install image to our thumb drive. Note that instead of using /dev/disk4. We are using /dev/rdisk4. In short /dev/rdisk will allow more direct access to the USB device and thus much better performance for writing our image.
  5. Plugin your USB to Serial Adapter, and connect the serial cable to the adapter and to the serial port on the pfSense box.
  6. Plugin your USB drive that has the pfSense image into the pfSense box.
  7. From terminal run ioreg -c IOSerialBSDClient | grep usb which show your usb to serial cable connected. If it doesnt, check that you don’t need special drivers installed.
  8. The output from the above command should show you an IODialinDevice such as /dev/tty.usbserial
  9. In Terminal run the following to attach to the console device
  10. Connect the power cord to the pfSense box.
  11. After a minute or two the device should boot and you can start configuring the device following this guide here.
  12. Port mapping is from left to right. re0, re1, re2 respectively.

Read More

Realtime (or not) Traffic Replication with Gor

As an operations engineer, we all have various tools that we rely on heavily. Those could be for load testing, monitoring, code analysis or deployments. One tool that I wanted to touch on is gor. Gor is an amazing tool which allows you to replay captured traffic in either real time or from capture files. This is incredibly useful when rewriting applications or even testing refactored changes. You can spot bottlenecks before you push changes to production.


Realtime Replication

Grab the latest build here and deploy or copy it up to machine that you want to be the replayer. The replayer will be mainly CPU bound. Once on this machine, you can run it with the following:

This will setup the replayer to listen on port 28020. Any incoming traffic to this port will have its host header rewritten to match and it will then be sent to limiting to 10 requests/second. You could also change the –output-http line to “|10%” to send 10% of incoming requests. Remove |10 or |10% entirely to just mirror 100% of traffic. For performance reasons, I would set GOMAXPROCS equal to the number of CPUs on this server.

Deploy or copy the latest build to a server that you want to replicate traffic from.

This will setup gor to capture traffic from port 80 and to send it to the replayer which we started running in the previous step.

Once these are both running (always ensure the replayer is running first) you will see requests start to hit your target.

Captured Replication

However, if you want to playback traffic faster than 100%, or for whatever reason don’t want to mirror live production traffic then you can create a capture file on the source machine.

Run this for however long you want. It will capture data from port 80 and dump it to requests.gor

Copy this file to our replayer and we can play this back.

Very similar to the first time we ran the replayer. However this will use our requests.gor file as a source, and replay it back at 200% speed. So if your production system saw 100/requests/second this should replay it back at 200/requests/second.

Note that there are a lot of other things you can do with gor and the readme has a lot of great documentation around its use. I have successfully used gor to replicate over 400k/requests/minute from one service to a staging service. In this case, I actually had to run multiple replayers.


Read More

Docker for Developers Part 2

The Saga continues

This is a continuation of my previous post which was an introduction to docker geared for developers. In the previous post, we got somewhat familiar with the docker ecosystem and built a very simple hello world application. In this part we are going to get dynamodb running locally, run a one off script to preload data, build a sample api service that reads from dynamo, and finally a sample website that reads from that api service. As with the first part, you can retrieve a copy of the completed code base off github or directly download the assets for part 2 here.

Running DynamoDB

Before we can really start building our API server, we need a place to store data. As we learned in the previous part, we can use docker-compose files to orchestrate the various services that make up our application. This could be a postgres or mysql instance, however in this case the application is going to leverage DynamoDB for storage. When developing the application it doesn’t make sense to create an actual dynamo table on AWS as you would incur some costs for something that is only going to be used for development. There are some caveats and limitations to this however which you can read about here. Since we would never want a local dynamodb container running in any environment other than development, we want to go ahead and edit docker-compose.override.yml with the following:

Save that file, and run docker-compose up -d and you should see output that the dynamodb container is running alongside our movie-api instance

Now that DynamoDB is running we need to be able to link our API container to it so that they can communicate. Our Compose file needs one small change..

Rerun docker-compose up -d which will recreate the movie-api-1 container.

Loading data into DynamoDB

Now that we have dynamodb running, we can show how to run a one off command to invoke a script that will seed data into dynamo. First, we can create the directory structure for storing our sample data and a place for our seed scripts. Within the movie-api directory create two new directors. One called scripts and the other data. We can use an amazon published sample data set for building this application. You can retrieve it here and extract this to the data directory.

The application directory structure should now look like this:

Now within the scripts directory lets write a simple script for seeding this data to our local dynamodb instance. Within the movie-api/scripts directory create a file called and open it for editing.


This script is pretty straightforward, this script will create the table within dynamodb if it doesnt exist, and then seed it with a little over 4,000 movies from the json file in our data directory.

Before we can run our script, we need to set a few environment variables on our movie-api instance. Go ahead and open up docker-compose.override.yml and adjust it to reflect the following:

The AWS credentials do not need to be anything real, in fact, leave them as above (foo, and bar). They just need to be set to prevent boto from barfing when connecting to the local dynamodb instance. In a production setting, we would leverage IAM roles on the instance to connect which is far more secure than setting credentials via an environment variable.

Once you have created the script, and also set the environment variables we can run the following to recreate the container with the new environment variables and then run the script.

As you can see, we ran the script that we created within the container. We do not need to have our python environment setup locally. This is great, because the containers environment is isolated from every other application we may be developing (and even other services within this application). This provides a high degree of certainty that if we deploy this image, that it will function as we expect. Furthermore, we also know that if another developer pulls this code down and runs the project it will work.

Building our API

Now that we have the data in our datastore  we can build a simple lightweight API to expose this information to a client. To keep things simple, we are going to create a simple endpoint which will return all the movies that were released in a specified year. Lets go ahead and open up movie-api/demo/services/ in our IDE and add a bit of code to make this happen.

Save this and then we can try querying our service with a simple curl command:

Building our Website

Now that we have an API that returns the data we need, we can create a simple web interface with a form that will present this data in a nice fashion. There are a few ways to make this happen, normally i’d recommend using something like angular however to further demonstrate container linking and such I will use a seperate flask app.

Within the movie-web directory we need to create our app skeleton. To simplify things, copy the Dockerfile, and requirements.txt files from movie-api to movie-web. Besides the copying of those 3 files, go ahead and create the following directory structure and empty files so that the output of tree matches the below.

In requirements.txt remove the boto reference and add in requests.

Open up and edit the third line to reflect the below

Go ahead and create demo/services/ and open it up within your IDE.

Now to get things running we just need to edit our docker-compose.yml and docker-compose.override.yml files.



With those changes saved. We can run docker-compose up -d which should launch our container. We can verify connectivity with curl.

Now lets create a couple templates, and a build out the endpoints on our webapp so that we can perform a simple search.

Open up demo/services/templates/index.html:

Open up demo/services/templates/results.html:

And finally edit demo/services/

If you visit your web browser at which should show the following:


Enter in 1993 and you should see the following results:


At this point that completes our application and this tutorial on docker for developers. To Recap, we installed and setup the docker toolbox on your machine. We then demonstrated how to use docker-machine, docker, and docker-compose to build a sample application that uses dynamodb, an api service, and finally a web application to view a sample dataset. You should be familiar now with creating a Dockerfile to build an image, and using compose to orchestrate and run your application. You have defined environment variables, container links, ports, and even leveraged the ability to map a volume coupled with flasks ability to reload on file changes to rapidly speed up development.

One thing you may have noticed is that we spent most of the time dealing with application code, and not a whole lot of time spent working with docker itself. Thats kind of the point. One of the greatest strengths of docker is that it simply gets out of your way. It mays it incredibly easy to rapidly iterate and start working on your application code.

While this wraps up my 2 part series on docker for developers, ill be writing additional posts centered around docker for QA and Operations Engineers. This will focus on testing, CI/CD, production deployments, service discovery, and more.

Read More

Docker for Developers Part 1



Recently I started working on a few projects where docker seemed like a great fit to rapidly speed up development of the project. In one case we wanted to build a prototype service that contained an API endpoint that utilized 4 micro services. The docker landscape is still green with a lot of toolsets not even existing for a year. While I feel the development side of things is great, the production deployment, auto scaling, and release management is still lacking. One of the projects I have been following closely is Rancher which seems to be on track to solve all of these things. This will be a series of posts initially focusing on development and the building of a fully featured sample application demonstrating the power of docker running locally. I will add posts documenting CI with Jenkins, through to a production deploy and management on AWS.

What will we do?

This tutorial is going to walk through the creation of a sample web application that utilizes a sample API service backed by dynamodb. Specifically we will:

  1. Layout the structure of our application and explain why things are laid out like they are.
  2. Build a sample hello world flask app that shows the initial power of docker and docker-compose.
  3. Run a local dynamodb container locally for development.
  4. Load some data into that local dynamodb install.
  5. Build a sample flask API that reads from that local dynamodb instance.
  6. Build a sample flask website that reads from the API and returns some basic data.


1. So to dive right into it, you will need two things installed on your machine to work through this tutorial. Currently everything below assumes you are running on OSX however it should work just fine under Linux as well.

  • Install Virtualbox:
  • Install the docker toolbox:

2.  Assuming that you have never used boot2docker before (and if you did, you should be prompted with instructions on how to convert to docker machine), run the following command to setup a default docker machine. This will be a virtual machine that will be where all the various containers run that you launch. More on that in a minute.

3. You can now run $ eval “$(docker-machine env default)”  to set the required environment variables. If you will be launching docker containers often, you might even elect to put this in your bashrc or zshrc file.

4. You should be able to run docker ps and see the following:


Helpful Commands

This area serves as a quick reference for various commands that may be helpful as a developer working with Docker. A lot of these may not make sense just yet, and thats fine. You will learn more about them below and can always come back to this one spot for reference.


Building our application

So now that the tooling is setup we can discuss what our project structure will look like.  You can see a completely functional and done copy of the below project on github and you can grab just the files we create here in part 1.

Initial Skeleton

  1. First create a directory called docker-movie-db-demo somewhere.
  2. Within that directory create two directories. One called movie-api and the other movie-web

It should look like this

We created two directories however, that are going to house two separate applications. The first of which, movie-api is going to be a simple flask API server that is backed by data in dynamodb. The second application called movie-www is going to have a simple web interface with a form, and allow the user to list movies from a certain year.

Our first Flask App

Within movie-api go ahead and create a few empty files and directories so that your structure matches the below. We will go through and touch these files one by one.

Open up and lets toss a few lines in.

This is pretty basic, but it will initialize a basic flask app from demo/services/ and listen on a port that is specified as the first argument when running python 5000.


Open requirements.txt and add in the following

This is also pretty straightforward, but we are ensuring we install flask. Boto is installed for interfacing with dynamodb. I’ll have to write a separate article on why its important to pin versions and the headaches that can solve down the line.


For now lets just add in the following:

We are adding a simple route for the index page of the api service that for now just returns the text “Hello World!”


The dockerfile is where the magic happens. Depending on how you are used to doing development, you might create a virtual env somewhere, or possible a vagrant image. This certainly works however you often end up with a bunch of files scattered everywhere, mismatches between your virtual env and someone elses (if you arent careful) and/or multiple vagrant images floating around that slow down your machine.

Open the Dockerfile (Note that the Dockerfile should have a capital D) up and paste in the following:

When running the build command, this tells the system how to build an image. In this case it will use a python 2.7 base image, copy the CWD (movie-api) to /code in the container, set the CWD to /code, run an apt-get update, pip install our requirements and then finally run our application. If you want more details you can read the dockerfile reference here which explains whats going on in detail and whats possible.

At this point we have enough of a skeleton to actually build a container, run it if we wanted to.

We just built an image, spun up a container based off that image, queried the service and got a response, stopped the service, and deleted the container. However, every time you make a code change your going to have to rerun the build command, and then relaunch your container. If your doing quick iterative development this can get annoying quickly. There is a better way.


 Introducing docker-compose

The docker-compose files are how we orchestrate the the building and running of our containers in an easier fashion. We can leverage how these files work with flasks built in reload system on file change to enable rapid iterative development

docker-compose.override.yml is special. When you run the docker-compose command, it will look for docker-compose.yml and docker-compose.override.yml. If present, it will go ahead and merge them and then perform actions based on that merged data.

We leverage this behavior to build our development environment. If we wanted a test environment for example, we would add a docker-compose.test.yml file, and when running docker-compose target that environment with docker-compose -f docker-compose.yml -f docker-compose.test.yml. However, this is generally only done by build systems, and so we use the override file for development as it keeps the command for developers simpler as they don’t need to specify -f.


Within the root of our project directory (docker-movie-db-demo) lets create a file called docker-compose.yml and make it look like so:

We have just defined a movie-api service that for now has no image, will always restart on failure, and exposes the container port 5000. You can see the full docker-compose file reference here

As mentioned above, the override file will allow us to override some things in the movie-api base compose definition to make development a little bit faster.

Create and edit a file called docker-compose.override.yml and make it look like so:

If you remember, in our dockerfile, we copy up the movie-api files into the image during its build. This is great when you want to make a container that you start shipping around to various environments such as test, staging, and production. However when you just want to do local development, building that same container every time is time consuming and annoying. With our override file, we have made it so that we will mount our code base within the running container. This allows us to use our favorite IDE locally to do development but immediately see those changes reflected. We also have exposed port 5000 on the container and mapped that to port 8080 on our docker-machine. This makes it a little bit easier to debug and test. In production you generally wouldn’t do this and i’ll detail more in a seperate article focusing around production deployment of this workflow.

Starting our movie-api app.

Now, from the root of our project directory (docker-movie-db-demo) run the following command:


You can tail the logs of the container by running:

So already you can see that starting up the container is simpler. However things really shine when you start editing code. Fire vim or your favorite IDE and edit movie-api/demo/services/

If you kept tailing the logs you will see that it instantly reloaded, and if you run curl again you will see that the output changes.


Wrapping it up

This concludes part 1 of the tutorial. In summary we laid out the structure for our project, went through how to setup a machine for docker, built a sample flask application that returns a hello world message. We also walked through how to make changes to the application and test those changes in realtime without having to build an image and redeploy the container.

In the next post, i’ll focus on adding a local dynamodb instance, how to run one off data scripts to do things like load data and build a sample web interface that interacts with the API.

Read More

Software RAID 1 with Xenserver 5.5

If you happen to have the need to run a software raid 1 setup with your xenserver install below are a simple step by step to get this working.

This setup assumes that during the install, you elected not to setup any storage repositories. I generally do that separately.

In the below, pay attention to — and ‘ as sometimes these characters get mangled during copy/pastes. Comments inline.

Reboot, and within the bios set your machine to boot of /dev/sdb. Once xenserver starts up verify that you have booted to the proper disk by running a df -h.

You should see something like..

Once verified, run the following to resetup /dev/sda

Now if you run mdadm --detail /dev/md0 you can see that its syncing data from /dev/sdb.

You can point your swap to /dev/md1. Note that ideally you would never use swap. If for whatever reason you regularly plan to use swap space, doing this will impact performance.

also don’t forget to edit /etc/fstab and change it to use /dev/md1

watch mdadm --detail /dev/md0 until the state is clean then reboot again and this time boot from sda.

This completes the setup, and you have properly tested that you can boot cleanly from either drive.

Read More

Connecting a chromecast to a wireless network that has a captive portal

In September I rented a large house in the Poconos that to my surprise required users to go through a captive portal before being able to access the internet. This is certainly common in hotel networks, and these days most consumer routers even offer this functionality although in my experience its rare to see it utilized. I’d say of the 3-4 dozen houses we have rented i’ve seen it maybe 2 or three times. Regardless, one thing I love to always have in my backpack is a spare chromecast as its great for streaming media from plex or netflix while on the go.

One thing that became immediately apparent was that the chromecast lacks the ability for the user to provide any type of input so if you run into a captive portal your out of luck. However there are a couple of ways to make this work. First, I would not recommend following the below steps to get your chromecast working if you are on a hotel network. While it will work (although they may restrict certain kinds of traffic) you open yourself up to having any other user also connected to the network. Most likely someone would start playing something random and screw with you. For this though there is still a solution. Look into getting a travel router. This would also easily work for the scenario above as well.

However, if you dont want to spend any coin and happen to have a *nix laptop handy theres an easy way to make this work. Temporarily spoof your chromecast’s MAC address on your laptop, auth with the captive portal, turn wifi off, reset your MAC back to factory setting, and use your phone to join the chromecast to the network.

I’ll show you how to do this for OS X.

First, record your current MAC Address by typing the following:

Copy ether 3c:15:c2:b8:ad:be to your notepad

in terminal, type the following:

This will change your MAC address. Then run the following commands

Now, reconnect to the wifi network, and auth with the captive portal

Once thats done, turn off your wifi card again and use your phone to go through the normal setup to put a chromecast on the network. This time it should properly connect.

Run the following to reset your MAC address to its original

All done!

Read More

Ubuntu 14.04 mdadm raid failure to boot due to failure to mount /root

A few days ago I had a drive fail in my software raid 1 array. Figuring this would be no big deal, I powered off the server, swapped out the bad drive for a good one and powered it back on. (and made sure to tell the bios to boot off the second good drive)

Initially things look to be loading great however I was suddenly dropped into busybox shell with the following message.

I attempted to mount the array manually but got an error that the device or resource was busy

I booted up with a live CD and could browse and access the data just fine.

Things that didn’t work but put here because it could help someone else:

From a live CD I tried running an fsck which reported back no errors. I forced one just to be safe however had no luck.

I mounted the array to /mnt, chrooted to /mnt and ran an initframfs-update.

I stumbled upon this bug which turned out to be the issue and the suggested solution worked great.

From my live cd and chrooted to the array.

Then rebooting was enough to get the OS to properly boot.


Read More

Redundant SFTP Servers with AWS: Route53

With Amazon I recently ran into a case where I needed an SFTP service that was accessible to the internet but could survive up to 2x AWS availability zone outages. Sadly, amazon only allows certain ports to be load balanced using the Elastic Load Balancers. Since we could not use any other port but 22 we were forced to look at a few solutions such as HAProxy, and some commercial solutions. The latter being very expensive from a licensing and time perspective.

In order to survive two availability zone outages any infrastructure that the SFTP process relies on also needs to be available. Below is a list of the various systems that are required for this whole process to run.

  • Shared Authentication system
  • Shared Filesystem
  • SFTP Server
  • Some way to automatically route traffic between healthy hosts.

I will touch upon each of these in seperate blog posts but for this one I want to discuss the overall arcitecture of this system.

For centralized authentication you have a few options. One of which would be some sort of master/slave LDAP system that spans availability zones. Another might be using SQLAuthTypes with an RDS instance.

One important thing is to ensure that your SFTP Servers share a common file system. This can be accomplished a myriad of ways, but if you want to go the cheaper route then I recommend GlusterFS. I wont dive into the setup of this too much as that could be a whole article in itself. However with AWS you would want three glusterFS servers each in an availability zone. You would configure a replica volume (Data is replicated across all three glusterFS servers) and this volume would be mounted on each SFTP server. In the event of a glusterFS failure the client would immediately start reading/writing from one of the other gluster servers.

Another important thing to remember is that you want things to be transparent to the end user. You want to sync the ssh host keys in /etc/ssh across the servers. This way if a user connects via SFTP and gets routed to a diffent SFTP server they wont get any warnings from their client.

What ties all of this together is route53. Amazon recently introduced the ability to create health checks within the DNS Service that they offer customers. The documentation for configuring health checks is certainly worth reading over.

First health checks must be external, so lets assume you have three servers with elastic IPs.

Hostname Public IP

We first want to configure three TCP health checks for the above IPs that check for port 22.


We then want to add record sets inside of the zone file we have. Make sure that you have the TTL set to either 30 or 60 seconds. In the event an instance goes down you want that record to be yanked out as quickly as possible. Depending on how you want to configure things (active/active or active/passive) you may want to adjust the Weighted value. If each record has the same weight then a record will be returned at random. If you weight sftp-1 to 10 and sftp-2/3 to 20 then sftp-1 will always be returned unless it is unavailable then either of the other two will.


As you can see, the final configuration shows three servers that will response for


Now that we have the above configured. If you run dig you will see that the TTL value is low, if you stop SFTP then that record will no longer be returned.

In my next post in this series, I will discuss glusterFS.

Read More

Elastic Load Balancing to VPC instances on a private subnet

Generally, when creating an elastic load balancer inside a VPC you are load balancing to a set of servers that are inside a public subnet (which routes through the IGW device). Each of these instances has it’s own elastic IP and while elastic IPs are free you may want to move your web servers inside a private subnet to avoid placing a public IP on them.

I have had discussions with amazon support and generally gotten mixed responses. The documentation is not exactly clear in the required configuration. First, the important thing to understand is that when you create a load balancer you are essentially creating hidden instances inside that subnet which have two IPs (a public and a private). If we wanted to have our public load balancer able to serve traffic from a set of servers on a private subnet then we need to create public subnets for the hidden elastic load balancer instances to live in.

Let’s say we have three webservers. Each of these are in a different availability zone and existing inside a private subnet which routes traffic through a NAT device. These instances do NOT have public IPs.

Hostname IP Address

We need to create three public subnets with the following address spacing

The subnets we just created are associated with our public routing table which will route traffic through the Internet Gateway Device. We can now create an Elastic Load Balancer using the documentation link above. When you get to the page to select the subnets you want to make sure you select the subnets we just created. You then want to select the instances that you wish to load balance traffic to. You should make sure you configure your health checks correct and assuming your security groups allow traffic from the Elastic Load balancers subnet or security group to the webservers your instances will soon report healthy and you will be able to access your content using the new load balancer URL (assuming your configuration permits this).

Read More

Akismet: Simple Comment Spam Filtering

So I have developed a few websites in the past year that included blogs. One of the big issues is comment spam. Normally the standard procedure has been to implement reCaptcha. While I do love helping to digitze books recently several methods have come to light forbypassing reCaptcha. The amount of Viagra comments started to get to an extreme. There are certainly some less creative solutions such as automatically dropping comments with certain keywords, or blocking comments from outside of the country. You could also simply block all comments by default and approve comments as they come but that inhibits active discussion on posts, and frankly is kind of crappy.

I did some research and happened upon Akismet. This service offers a pay as much as you want model which I love. You can pay as little as nothing or as much as 120/yr. Now that I found a service the question came down to implementation. I found this fantastic gem called rakismet which offers easy integration and provides helper methods for checking if a message is spam, and best of all, letting them know if a comment was truly spam or not via api calls.

I wont dive into the setup of the Gem because the instructions are really fantastic. I do want to briefly talk a bit about a great way to implement this functionality to place comments marked as spam into a queue for approval or deletion.

My Comment Model includes:

The important thing here is really the approved bit, as by default, comments are approved. When you iterate of your comments you can check to see if approved is true before displaying the comment in your view.

Our comment controller might look something like…

The important thing to note in the exampe above is the spam method. When we call it on comment this reaches out to the Akismet API and checks the comment to see if its considered spam. The API will return either true or false, which if true means the comment is spam and thus sets its approved status to false. You can now leverage this data in your admin panel or whever to see your comments that are pending approval.

So the best thing about this service, is that you get to help improve it. You can use something like the below to help Akismet learn which comments are spam and which arent.


Overall my experience with the service so far has been excellent. Integration is easy with any rails app, documentation is excellent, and you cant beat the pricing model of pay whatever you want. You’re users are happy because they dont have to try to guess the hard to read words, solve a math problem, orselect the cats.

TL;DR: Captcha’s are dead. Get rid of them, and start effortlessly filtering comments with Akismet.

Read More