97

I've seen various config examples for handling dual-stack IPv4 and IPv6 virtual hosts on nginx. Many suggest this pattern:

listen 80;
listen [::]:80 ipv6only=on;

As far as I can see, this achieves exactly the same thing as:

listen [::]:80 ipv6only=off;

Why would you use the former? The only reason I can think of is if you need additional params that are specific to each protocol, for example if you only wanted to set deferred on IPv4.

Synchro
  • 2,983
  • 5
  • 25
  • 35

5 Answers5

86

If you host multiple vhost domains with a single Nginx instance, you can't use the single combined listen directive

listen [::]:80 ipv6only=off;

for each of them. Nginx has a weird quirk where you can only specify the ipv6only parameter once for each port, or it will fail to start. That means you can't specify it for each vhost domain server block.

As Michael mentioned, starting with Nginx 1.3.4, the ipv6only parameter defaults to on.

Therefore, if you want to host multiple domains on both IPv4 and IPv6 with a single Nginx server, you are forced to use two listen directives for each domain server block:

listen 80;
listen [::]:80; 

Additionally, as Sander mentioned, using ipv6only=off has the drawback that IPv4 addresses are translated to IPv6. This can cause problems if your app does IP checking against blacklists like Akismet or StopForumSpam because unless you build in a reverse translation layer, your app will check the IPv6 translation of the spammer's IPv4 address, which won't match any of the IPv4 addresses in the blacklist.

Jeff Widman
  • 2,285
  • 3
  • 22
  • 20
  • 2
    Yes, that's the same as I mentioned about `deferred`, and other per-protocol directives. It would be useful if they could be specified separately from the listen directive for the reason you say. – Synchro Apr 24 '15 at 10:22
  • 1
    And the core of the matter is, you need to specify listen directive for each domain separately. Otherwise what would happen ? site would work fine via ipv4 and via ipv6 it would show the nginx welcome page. ROFL – Silver Moon Dec 09 '15 at 06:57
  • 2
    Thank you for the thorough explanation! I was getting a confusing error when I specified `ipv6only=off` for the same port twice. Your answer solved the problem! –  Apr 24 '16 at 14:38
  • 2
    Also if you want to use 2 vhosts both listening to 443: ```listen 443; listen [::]:443;``` . Using ```listen [::]:80 ipv6only=off;``` will throw an nginx error that port is already in use – lukeaus Jul 20 '16 at 06:37
66

That probably is about the only reason you would use the former construct, these days.

The reason you're seeing this is probably that the default of ipv6only changed in nginx 1.3.4. Prior to that, it defaulted to off; in newer versions it defaults to on.

This happens to interact with the IPV6_V6ONLY socket option on Linux, and similar options on other operating systems, whose defaults aren't necessarily predictable. Thus the former construct was required pre-1.3.4 to ensure that you were actually listening for connections on both IPv4 and IPv6.

The change to the nginx default for ipv6only ensures that the operating system default for dual stack sockets is irrelevant. Now, nginx either explicitly binds to IPv4, IPv6, or both, never depending on the OS to create a dual stack socket by default.

Indeed, my standard nginx configs for pre-1.3.4 have the first configuration, and post-1.3.4 all have the second configuration.

Though, since binding a dual stack socket is a Linux-only thing, my current configurations now look more like the first example, but without ipv6only set, to wit:

listen [::]:80;
listen 80;
Michael Hampton
  • 237,123
  • 42
  • 477
  • 940
  • 7
    Some operating systems do not do dual ipv4 and ipv6 sockets at all, like OpenBSD, so for that you are going to have to listen twice. – Justin Cormack Mar 09 '15 at 20:47
  • @JustinCormack Yes, you're right, and I've taken that into account for some time. Just hadn't updated this post until now. – Michael Hampton Feb 02 '17 at 01:59
  • 1
    `listen localhost:8080;` seems to listen to both (1.12.2) and using `proxy_pass http://localhost:8080` would load balance between ::1 and 127.0.0.1 - I had to add a line for ipv6 to get real ip in logs `set_real_ip_from 127.0.0.1; set_real_ip_from ::1; real_ip_header X-Forwarded-For;` – Antony Gibbs Feb 06 '18 at 17:02
17

With the ipv6only=off configuration style the IPv4 addresses might be shown as IPv6 addresses using the (software-only) IPv4-mapped IPv6 addresses in for example log files, environment variables (REMOTE_ADDR) etc.

Sander Steffann
  • 7,572
  • 18
  • 29
7

To my understanding (and according to the docs at http://nginx.org/en/docs/http/ngx_http_core_module.html#listen), using just listen 80; is sufficient if you wish to channel both IPv4 & IPv6 traffic at the same port.

Revised answer as of Nov. 2021

As of Nov. 2021 with Nginx latest (from the official repo) e.g. on Ubuntu 18.04 or 20.04 I can confirm that for regular (=not the default) Nginx vhosts this is what works for both IPv4 & IPv6 traffic:

listen [::]:80;

...and if you use a separate block for HTTPS traffic:

listen [::]:443 ssl http2;

The ipv6only=off flag should ONLY be referenced once and in the "default" vhost in Nginx (the one used by Nginx when no domain can be mapped to a vhost).

E.g.:

server {
    listen [::]:80 default_server ipv6only=off;

    # rest of your Nginx vhost config goes here...
}

server {
    listen [::]:443 default_server ssl http2 ipv6only=off;

    # rest of your Nginx vhost config goes here...
}

Obviously, if your Nginx setup uses a single vhost then you need the latter config only.

fevangelou
  • 171
  • 1
  • 4
  • 1
    That has already been established, and mentioned in the question. Please see the other answers for the difference. – Synchro Mar 09 '17 at 07:46
  • 4
    It did not for me, I needed both. wget and curl where failing when using ipv6 until I added the line "listen [::]:80 ipv6only=on;" – Basil A Mar 30 '17 at 14:55
  • This doesn't work for me, and I can't find where in the docs it says it would work. – gsgx Jul 23 '20 at 07:15
  • Perhaps state the nginx version (`nginx -V`). I'm not clear on what "official repo" means. – Paul Nov 15 '21 at 18:27
  • That's why I mention the date. The official repo (from nginx.org @ https://nginx.org/en/download.html) lists v1.20.1 as current stable release. – fevangelou Nov 15 '21 at 20:00
  • The Ubuntu "official" repos are not on 1.20, which is why it is better to state the version number. – Paul Nov 16 '21 at 00:15
  • Official repo is the one from the software vendor (Nginx), not Ubuntu. Unless you wish to use an older version that is... – fevangelou Nov 16 '21 at 15:10
  • At best, that is you opinion. Ubuntu repos are built with modules nginx does not include, and those modules most people and other packages assume to be present. So "officlal" nginx by your definition won't work well for a whole bunch of Ubuntu users, but the "official" Ubuntu repos will. Unless you consider the "official" Ubuntu repos aren't "official". – Paul Nov 16 '21 at 17:13
0

One pesky problem I have encountered while adding IPv6 support to a site with the listen [::]:80 ipv6only=off; snippet, was when I added it to a vhost and the default_server was already configured to listen for both 80 and [::]:80.

nginx refused to start, complaining that the address was already in use!

Replacing the magic listen [::]:80 ipv6only=off; with the two traditional listen lines allows nginx to start just fine.

As much as listen [::]:80 ipv6only=off; may be convenient in manual configuration, it may cause nasty troubles when used in automated configuration systems.