Fix Gunicorn "Address already in use"
Fix Gunicorn Connection in use and Address already in use errors: find the process holding the port or socket, stop duplicate services, and prevent stale sockets.
Problem statement
Gunicorn fails to start and repeats:
[ERROR] Connection in use: ('127.0.0.1', 8000)
[ERROR] Retrying in 1 second.
...
[ERROR] Can't connect to ('127.0.0.1', 8000)
or, underneath, the operating system error:
OSError: [Errno 98] Address already in use
Something is already listening on the address Gunicorn wants to bind. That "something" is usually another Gunicorn: a copy started by hand in an SSH session and never stopped, a second systemd unit for the same app, a previous container that did not exit, or runserver left running on port 8000. Less often it is an unrelated service using the same port.
The error is protective: two processes cannot both own a listening TCP port, and if they could, requests would be split randomly between old and new code.
Quick answer
- Find the process holding the address:
sudo ss -ltnp 'sport = :8000'. - Identify how it was started:
ps -o pid,ppid,user,etime,cmd -p <PID>andsystemctl status <PID>. - Stop it through its manager (systemd, Docker, Supervisor) rather than killing it blindly, so it does not restart.
- Remove duplicate service definitions and old manual processes.
- Start the intended service and confirm exactly one master process owns the address.
- Prefer Unix sockets managed by systemd (
RuntimeDirectory=) to avoid port collisions between apps.
Step-by-step solution
1. Find who is listening
For a TCP port:
sudo ss -ltnp 'sport = :8000'
Output:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 2048 127.0.0.1:8000 0.0.0.0:* users:(("gunicorn",pid=2211,fd=5),("gunicorn",pid=2212,fd=5))
Several PIDs sharing the same file descriptor are one Gunicorn master and its workers. The lowest PID is usually the master; confirm with the parent PID.
lsof gives the same information if installed:
sudo lsof -iTCP:8000 -sTCP:LISTEN
2. Find out what started it
ps -o pid,ppid,user,etime,cmd -p 2211
systemctl status 2211 --no-pager
systemctl status <PID> shows which unit owns a process. Typical results:
gunicorn.serviceormyapp.service: a systemd unit is already running the app. Check for a second unit you just created for the same app.session-12.scope: it was started from an interactive SSH session, for examplegunicorn config.wsgirun manually or insidetmuxorscreen.docker-<id>.scopeorcontainerd: a container publishes the port.supervisor.service: an older Supervisor configuration still manages the app.
The elapsed time (etime) often tells the story: a process running for 40 days predates your current deployment approach.
3. Stop the process through its manager
Stopping through the manager prevents automatic restarts:
# systemd
sudo systemctl stop old-gunicorn.service
sudo systemctl disable old-gunicorn.service
# Supervisor
sudo supervisorctl stop myapp
# Docker
docker ps --filter "publish=8000"
docker stop <container-id>
For a manual process started in an old session, stop the master gracefully:
sudo kill -TERM 2211
sleep 5
sudo ss -ltnp 'sport = :8000'
Gunicorn handles SIGTERM as a graceful shutdown: workers finish current requests up to the graceful timeout. Use kill -KILL only if the process does not exit, and kill the master PID, not individual workers, because the master respawns workers.
4. Remove duplicates permanently
List units that might run the same app:
systemctl list-units --type=service --all | grep -i -E "gunicorn|myapp|django"
ls /etc/systemd/system/*.service | xargs grep -l "gunicorn" 2>/dev/null
Remove or disable old units, and delete old Supervisor program files in /etc/supervisor/conf.d/. Then:
sudo systemctl daemon-reload
Check crontab entries with @reboot that start Gunicorn:
sudo crontab -l 2>/dev/null | grep -i gunicorn
crontab -l -u deploy 2>/dev/null | grep -i gunicorn
5. Unix sockets behave differently
Unix socket binds rarely produce this error. When Gunicorn binds to a socket path and a socket file already exists there, it removes the existing file and creates a new one. That avoids failures from stale sockets after a crash, but it has a side effect: if an old Gunicorn is still running, the new one silently takes over the path, and the old master keeps running without receiving any connections, holding memory and possibly running old scheduled code.
Check for more than one Gunicorn master for the same app:
ps -eo pid,ppid,etime,cmd | grep "[g]unicorn: master"
sudo ss -xlp | grep gunicorn.sock
The gunicorn: master process title is set when the setproctitle package is installed; without it, look for Gunicorn processes whose parent PID is 1 or a systemd scope. Stop any extra master as in step 3.
If the path exists but is not a socket (for example a regular file created by a mistake), Gunicorn refuses to start with an error saying the path is not a socket. Remove the file and start the service.
Keep socket paths inside a directory that systemd manages:
[Service]
RuntimeDirectory=myapp
ExecStart=/srv/myapp/venv/bin/gunicorn config.wsgi:application --bind unix:/run/myapp/gunicorn.sock
systemd creates /run/myapp on start and removes it on stop, so socket files never outlive the service that owns them.
6. Port conflicts with other services
If the process is not Gunicorn at all (for example another app, a monitoring agent, or node), choose a different port or, better, switch to a Unix socket so each app has a unique path instead of competing for a small set of ports:
--bind unix:/run/myapp/gunicorn.sock
Update the Nginx proxy_pass accordingly.
7. Restart loops that cause the error
A unit with Restart=always that crashes during startup can leave a moment where the old process has not fully released the port when the new one starts. If the journal shows rapid restarts, fix the underlying crash first. A short RestartSec=2 also avoids hammering the port during repeated failures.
Similarly, avoid starting a new Gunicorn during a deploy before the old one has stopped. Use systemctl restart, which stops and then starts, or systemctl reload for Gunicorn's graceful worker replacement (with ExecReload=/bin/kill -HUP $MAINPID), which keeps the same master and listening socket.
8. Verify
sudo systemctl start myapp
sudo ss -ltnp 'sport = :8000'
systemctl status myapp --no-pager | head -12
curl -sI http://127.0.0.1:8000/ -H "Host: www.example.com" | head -1
Verification check:
- exactly one Gunicorn master (with its workers) owns the address
- that master belongs to the intended systemd unit
- requests return responses from the current release
- after
sudo systemctl restart myapp, the service comes back without "Connection in use" messages
Explanation
A listening socket is owned by one process (and inherited by its forked workers). Gunicorn's master binds the address once and shares it with workers. A second master started for the same address cannot bind, retries a few times, and exits. The persistent fix is organizational rather than technical: make one process manager responsible for each app and remove every other way it can be started.
Edge cases / notes
Old code still being served
If a manually started Gunicorn from an earlier release still owns the port while your systemd service keeps failing, users are seeing old code. This often looks like "my deploy did nothing". Check the process's working directory with sudo readlink /proc/<PID>/cwd.
IPv4 and IPv6 bindings
Binding to 0.0.0.0:8000 and [::]:8000 from different processes can conflict depending on the system's dual-stack settings. Bind to 127.0.0.1 for apps behind a local proxy.
Containers with host networking
With network_mode: host, container processes bind directly on the host, so they conflict with host services on the same port.
Socket activation to avoid bind conflicts
systemd can own the listening socket and pass it to Gunicorn. The socket exists independently of the service, so restarts never race for the address, and connections that arrive while Gunicorn restarts wait in the socket's backlog instead of failing.
/etc/systemd/system/myapp.socket:
[Unit]
Description=myapp Gunicorn socket
[Socket]
ListenStream=/run/myapp.sock
SocketUser=www-data
SocketMode=0600
[Install]
WantedBy=sockets.target
/etc/systemd/system/myapp.service:
[Unit]
Description=myapp Gunicorn
Requires=myapp.socket
After=network.target
[Service]
Type=notify
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp/env
ExecStart=/srv/myapp/venv/bin/gunicorn config.wsgi:application --workers 3
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
Gunicorn detects the socket passed by systemd through the LISTEN_FDS environment variable and uses it instead of binding itself, so the service needs no --bind option. With SocketUser=www-data and mode 0600, only Nginx can connect.
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.socket
sudo systemctl start myapp.service
Point Nginx at unix:/run/myapp.sock.
Finding manually started copies quickly
A one-liner that lists every Gunicorn master with its owning unit and working directory helps during incidents:
for pid in $(pgrep -f "gunicorn" ); do
ppid=$(ps -o ppid= -p "$pid" | tr -d ' ')
if ! pgrep -f gunicorn | grep -qx "$ppid"; then
unit=$(systemctl status "$pid" 2>/dev/null | head -1)
echo "master pid=$pid cwd=$(sudo readlink /proc/$pid/cwd) unit=${unit:-unknown}"
fi
done
A Gunicorn process whose parent is not another Gunicorn process is a master. Masters that are not part of your expected service unit are candidates for cleanup.
Common mistakes
- Killing Gunicorn workers instead of the master, which immediately spawns replacements.
- Running
gunicornmanually "to test" on the production port and closing the SSH session without stopping it (it may keep running insidetmuxor withnohup). - Creating a new unit name during a migration without disabling the old one.
- Deleting the socket file while the old master is still running, which leaves an orphaned process serving nothing.
Next steps
- For other startup failures, see Fix a Gunicorn systemd Service That Fails to Start.
- If Nginx cannot reach Gunicorn afterwards, see Fix Nginx Not Connecting to Gunicorn.
- If deploys seem to have no effect, see Fix Django Code Changes Not Showing After Deploy.
FAQ
How do I find what is using port 8000 on Linux?
Run sudo ss -ltnp 'sport = :8000' or sudo lsof -iTCP:8000 -sTCP:LISTEN. Both show the process name and PID listening on the port.
Should I kill the Gunicorn process holding the port?
Stop it through whatever started it: systemctl stop, supervisorctl stop, or docker stop. If it was started manually, send SIGTERM to the master process.
Does the error happen with Unix sockets?
Rarely. Gunicorn replaces an existing socket file at its bind path, so a second instance starts without an error while the first keeps running unused. Check for duplicate master processes and let systemd manage the socket directory.
How do I restart Gunicorn without "Address already in use"?
Use systemctl restart so the old process stops first, or reload gracefully with SIGHUP, which keeps the existing master and socket while replacing workers.