My site is currently running off of an nginx HTTP server. In attempting to set up the site to respond to IPv6 requests, I ran into all sorts of problems. They all started off with the following error:

[gaarai@linode /etc/nginx]$ sudo service nginx restart
 * Restarting nginx nginx
nginx: [emerg] 807#0: no host in ":80" of the "listen" directive in /etc/nginx/sites-enabled/chrisjean.com:7

I poked around a bit more and ended up angering the nginx gods.

[gaarai@linode /etc/nginx]$ sudo service nginx restart
 * Restarting nginx nginx
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:443 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:443 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:443 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:443 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:443 failed (98: Address already in use)
nginx: [emerg] still could not bind()

I then found out that the guides I used were quite old and recommended an old syntax that no longer works. I used the following syntax as recommended in the guides:

server {
    listen :80;
    listen [::]:80;
}

This no longer works and will result in the problems listed above.

There are two solutions to the problem.

First Solution

The first solution is to remove the colon from in front of the IPv4 port and to specify ipv6only=on for the IPv6 port. It turns out that in Linux, binding to an IPv6 TCP port automatically binds to the IPv4 port as well. This explains the error messages about bind failures. Using ipv6only=on ensures that the IPv6 listen directive will only bind to IPv6 and not automatically to IPv4 as well.

server {
    listen 80;
    listen [::]:80 ipv6only=on;
}

Second Solution

The second solution is to change all of the listen directives for each server block to use the IPv6 format, for example:

server {
    listen [::]:80;
}

Note that there is just a single listen directive. Since by default, an IPv6 listen directive will bind to both IPv4 and IPv6 TCP ports, only the single listen directive is needed to cover both IPv4 and IPv6.

I list this solution second as 1) you may not want all of your server blocks to be available on both IPv4 and IPv6 (keeping them separate gives more control), 2) it may take a large amount of work to change all of the server blocks, and 3) if even one block is not updated properly, the server will be taken down. It is important to note that adding this listen directive is necessary even for server blocks that do not list a listen directive as the default is listen *:80, which only binds to IPv4.

Take the following block as an example.

server {
    server_name .gaarai.com;
    rewrite ^ https://chrisjean.com$request_uri? permanent;
}

This is a server block that causes redirection of one of my older domains to my current domain. This needs to be updated to the following:

server {
    listen [::]:80;
    
    server_name .gaarai.com;
    rewrite ^ https://chrisjean.com$request_uri? permanent;
}

Without this change, if I’m updating my server blocks to use the second method, restarting nginx will once again start spewing the messages about bind failures:

[gaarai@linode /etc/nginx]$ sudo service nginx restart
 * Restarting nginx nginx
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()

Using Explicit Addresses

Some of you may wonder about server blocks that have to have explicit address designations. In situations such as these, using the first solution would be the best option as it gives you the most control over how to declare everything. This is how such a server block would look for my server:

server {
    listen 72.14.185.165:80;
    listen [2001:470:1f0e:4da::2]:80;
}

When using this method, you can’t use the listen [::]:80; directives as in the second solution since you’ll run into binding issues again.

Summary

First solution:

  • Pros
    • More flexibility
    • Easier to maintain with complex setups
    • Easier to maintain when listening to address-specific ports
  • Cons
    • Adds an extra line to each server block (I admit that it’s a sad excuse for a con)

Second solution:

  • Pros
    • Looks cleaner having just the one listen directive for each server block
    • Ensures that all the server blocks are configured on IPv6 and IPv4 as errors will occur if a single block is left in just IPv4
  • Cons
    • Can cause more headaches in complex setups as you won’t be able to use address-specific listen directives
    • Can cause many more errors than the first solution until all of the server blocks are updated to use the single listen directive properly

What am I using on my server? After ensuring that I understood both approaches, I went with the first solution. The idea of going through and reconfiguring everything later if/when I have to make some more complex changes doesn’t sound very appealing to me.

Misc Details

My server is hosted at Linode and is currently running Ubuntu Server 13.10 with nginx version 1.4.1. Since Linode supports IPv6, my server was already configured to have IPv6 addresses available. If you have a server that does not have IPv6 allocations yet, you can use Hurricane Electric’s Tunnel Broker to get an IPv6 tunnel that directs IPv6 traffic to your server’s IPv4 address.

More details about nginx’s listen directive can be found in the official documentation.

Did I help you? Send me a tip.