'Address already in use' with IPv6 when upgrading executable through USR2 & QUIT

My issue:

I have an nginx server that listens on both IPv4 & IPv6:

server {
    listen       80 default;
    listen       [::]:80 default;
    server_name  test.example.com;
}

server {
    listen       80;
    listen       [::]:80;
    server_name  test_2.example.com;
}

A simple restart works fine. But that also cuts off running requests, hence I want to do a binary upgrade path through USR2 & ending with a QUIT on the old binary.

And here I run into this issue:

2025/12/11 14:34:45 [notice] 82937#112587: using inherited sockets from "7;"
2025/12/11 14:34:45 [emerg] 82937#112587: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 14:34:45 [emerg] 82937#112587: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 14:34:45 [emerg] 82937#112587: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 14:34:45 [emerg] 82937#112587: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 14:34:45 [emerg] 82937#112587: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 14:34:45 [emerg] 82937#112587: still could not bind()
nginx: [emerg] still could not bind()

At that moment the nginx service stops in its entirety.

Just listening on IPv4 works fine in combination with using US2and QUIT for a graceful binary upgrade.

How I encountered the problem:

Explained in the previous section.

Why does this work for IPv4 but not for IPv6?

Solutions I’ve tried:

  1. Listen on IPv6 only, no IPv4:

    server {
        listen       [::]:80 default;
        server_name  test.example.com;
    }
    

    This fails with the Address already in use error.

  2. ipv6only=off

    server {
        listen       [::]:80 default ipv6only=off;
        server_name  test.example.com;
    }
    

    This makes it listen on both IPv4 & IPv6, great!
    But, I have multiple server blocks with the same listen definition and that ends up in:
    nginx: [emerg] duplicate listen options for [::]:443

    And ipv6only=off can only be defined once.

  3. Specific IPv6 address

    server {
        listen       80 default;
        listen       [2001:1234:4567:0:10:20:30:40]:80 default;
        server_name  ``test.example.com``;
    }
    

    This works in that ā€˜nginx upgrade’ is no longer an issue. But the main server block should listen on [::]:80 default as a sort of catch all.

  4. reuseport

    server {
        listen       80 default;
        listen       [::]:80 default reuseport;
        server_name  test.example.com;
    }
    

    This seems to work fine, the IPv4 line doesn’t need reuseport because that line never gives an issue to begin with. With the IPv6 line reuseport resolves the issue by detaching quicker/properly from sockets?
    Output of 3 ā€˜upgrade’ runs:

    2025/12/11 16:16:09 [notice] 21899#112927: using inherited sockets from "7;"
    2025/12/11 16:16:15 [notice] 34956#139305: using inherited sockets from "8;"
    2025/12/11 16:16:17 [notice] 48455#114917: using inherited sockets from "7;"
    

    I’d prefer the old solution without reuseport because I’m not 100% clear on its security implications. I did see a CVE for FreeBSD about this but its also already solved in the p5 patch release and didn’t apply to our situation.

Version of NGINX or NGINX adjacent software (e.g. NGINX Gateway Fabric):

nginx 1.28.0

Deployment environment:

FreeBSD 14.3-RELEASE-p5

Tested directly on the host and within a jail with the same result.

Minimal NGINX config to reproduce your issue

worker_processes  1;

error_log  /var/log/nginx/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       [::]:80 default;
        server_name  test.example.com;

        location / {
            root   /usr/local/www/nginx;
            index  index.html index.htm;
        }
    }

    server {
        listen       [::]:80;
        server_name  test_2.example.com;

        location / {
            root   /usr/local/www/nginx;
            index  index.html index.htm;
        }
    }
}

NGINX access/error log:

Only error log applies here:

2025/12/11 15:40:22 [notice] 61472#128444: using inherited sockets from "7;"
2025/12/11 15:40:22 [emerg] 61472#128444: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 15:40:22 [emerg] 61472#128444: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 15:40:22 [emerg] 61472#128444: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 15:40:22 [emerg] 61472#128444: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 15:40:22 [emerg] 61472#128444: bind() to [::]:80 failed (48: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (48: Address already in use)
2025/12/11 15:40:22 [emerg] 61472#128444: still could not bind()
nginx: [emerg] still could not bind()

Additional information:

The nginx install within FreeBSD comes with a useful rc script that does the ā€˜upgrade’ for us e.g. using USR2 & QUIT signals to do executable upgrade.

I found a similar issue, Problem upgrading on the fly when ipv6 is used, from back in 2010 where it claims to be solved in the 0.8.39 release? I can’t verify that version now, I haven’t even tried to build that yet, probably many things won’t build with 15 year old software :slight_smile:

Commit 23f904e claims to fix this issue?
But later in 2016 commit fd064d3 changes that same logic.
(I’m a new user, sorry for the lack of links to the nginx github page for these commits)

But I have no idea if that made the problem an issue again or not?
My code-foo is too lacking in C and nginx in general that I’m a bit out of depth here.

3 Likes

Hi @maospr ,

Sorry for the delay. I’ve just tried to reproduce your issue on FreeBSD 14.3-RELEASE-p5 + nginx 1.28.0, and the binary upgrade path via USR2 works without any issues. FreeBSD isn’t very popular these days, but I haven’t heard of any problems from the community related to binary upgrades + ipv6.

PS. nginx 1.29.3 works fine as well in my env

Seeing as the ā€œsecurity implicationsā€ linked to from the docs points to the Linux socket(7) man page. I assume it’s related to this

To prevent port hijacking, all of the processes binding to the
same address must have the same effective UID.

So if you have nginx running as the ā€œnginxā€ user and nothing else running as that, you should be OK.

But that’s a Linux thing and not relevant to FreeBSD.

Hi @route443,

Thanks for the reply.

Think I’ve narrowed it down what the issue is.
I have multiple IPv4 & IPv6 aliases on the host its network interface.

On a different computer I reproduced the same but smaller setup. One host NIC, one jail.
This resulted in 2 IPv4 and 2 IPv6 addresses on the host NIC.
Doing the upgrade on nginx triggered the same Address already in use issue.

Then I switched the single jail to a vnet setup within FreeBSD. The jail then has its own network stack. The host NIC now only has its ā€˜own’ IPv4 and IPv6 address. And likewise for the jail.
Now when I upgrade Nginx it works fine, also without the reuseport option on the listen line.

@ac000 You’re right, this refers to Linux only. I can’t find any such related concern with current FreeBSD.
Thanks for pointing out I missed that its a Linux detail!

I’ve removed all IPv6 from the jail config (without vnet), only listening on IPv4 and I’ve got the same issue.
With vnet, again, no issue.

Something is going on in FreeBSD when jails are bound to the host its NIC.

If I can find anything I’ll report back.

Oh, these are excellent findings, @maospr !

The jail(8) man states that when ip4.addr is set, ā€œattempts to use wildcard addresses silently use the jailed address instead.ā€
So inside a non-VNET jail, when nginx ā€œtinksā€ it has bound to [::]:80 or *:80, the kernel may have actually bound it to a single specific jail ip instead, AFAIU.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.