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:
-
Listen on IPv6 only, no IPv4:
server { listen [::]:80 default; server_name test.example.com; }This fails with the
Address already in useerror. -
ipv6only=offserver { 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 samelistendefinition and that ends up in:
nginx: [emerg] duplicate listen options for [::]:443And
ipv6only=offcan only be defined once. -
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
serverblock should listen on[::]:80 defaultas a sort of catch all. -
reuseportserver { listen 80 default; listen [::]:80 default reuseport; server_name test.example.com; }This seems to work fine, the IPv4 line doesn’t need
reuseportbecause that line never gives an issue to begin with. With the IPv6 linereuseportresolves 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
reuseportbecause 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 ![]()
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.