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.