When working with Ubuntu 18.04 servers (or similar Linux distributions) that experience frequent public IP changes, many admins report a peculiar behavior: SSH connections become impossible immediately after an IP change - even when using the correct new IP address - until the SSH service is restarted.
The issue stems from how OpenSSH binds to network interfaces by default. In Ubuntu 18.04's default configuration, sshd binds specifically to the IP address available at service startup through the ListenAddress
directive in /etc/ssh/sshd_config
.
When the IP changes, the existing SSH daemon continues listening on the old IP address that no longer exists on the system. The service needs to be restarted to bind to the new address.
Modify the SSH daemon configuration to listen on all available interfaces rather than specific IPs:
# Edit the sshd config file sudo nano /etc/ssh/sshd_config # Change or comment out any ListenAddress directives # ListenAddress 0.0.0.0 # Uncomment if exists # Alternatively, explicitly set to listen on all interfaces ListenAddress 0.0.0.0 ListenAddress ::
After making changes, reload the SSH service:
sudo systemctl restart ssh
For systems where you can't modify the SSH config (or need backward compatibility), create a systemd service to monitor IP changes:
#!/bin/bash # /usr/local/bin/ssh-ip-watcher.sh current_ip=$(ip addr show eth0 | grep "inet " | awk '{print $2}' | cut -d/ -f1) last_ip=$(cat /var/tmp/last_ip 2>/dev/null) if [ "$current_ip" != "$last_ip" ]; then echo "IP changed from $last_ip to $current_ip - restarting SSH" systemctl restart ssh echo "$current_ip" > /var/tmp/last_ip fi
Create a systemd service file:
# /etc/systemd/system/ssh-ip-watcher.service [Unit] Description=SSH IP Change Watcher After=network.target [Service] ExecStart=/usr/local/bin/ssh-ip-watcher.sh Restart=on-failure User=root [Install] WantedBy=multi-user.target
And a timer to run it every minute:
# /etc/systemd/system/ssh-ip-watcher.timer [Unit] Description=Run SSH IP watcher every minute [Timer] OnBootSec=1min OnUnitActiveSec=1min [Install] WantedBy=timers.target
While solving the connection issue, consider implementing a dynamic DNS solution for easier access:
#!/bin/bash # ddns-updater.sh API_KEY="your_api_key" DOMAIN="yourdomain.dyn.com" INTERFACE="eth0" NEW_IP=$(ip addr show $INTERFACE | grep "inet " | awk '{print $2}' | cut -d/ -f1) curl "https://members.dyndns.org/v3/update?hostname=$DOMAIN&myip=$NEW_IP" \ -H "Authorization: Basic $API_KEY"
Set this script to run via cron after network comes up:
@reboot sleep 30 && /path/to/ddns-updater.sh */5 * * * * /path/to/ddns-updater.sh
After implementing any solution, verify SSH is listening on all interfaces:
ss -tulnp | grep ssh
Should show:
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* tcp LISTEN 0 128 [::]:22 [::]:*
Many sysadmins encounter this frustrating scenario: You've set up SSH access to an Ubuntu 18.04 server with a dynamic public IP, only to find connections fail after IP changes - even when using the correct new address. The server becomes completely unreachable until someone physically restarts the SSH service.
The root cause lies in how Ubuntu 18.04's networking stack and SSH daemon interact with dynamic IP changes. By default, sshd binds to specific interfaces at startup and doesn't properly re-bind when IP addresses change. This creates a mismatch between the actual network configuration and the service's binding state.
# Check current SSH bindings (run on server)
sudo ss -tulnp | grep sshd
Instead of manually restarting sshd, we can configure systemd to properly handle IP changes:
# 1. Edit the sshd socket unit file
sudo nano /etc/systemd/system/ssh.socket.d/listen.conf
# Add these directives:
[Socket]
ListenStream=0.0.0.0:22
ListenStream=[::]:22
FreeBind=yes
# 2. Reload systemd and restart services
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket sshd.service
For systems using NetworkManager, create an automatic restart trigger:
# Create dispatcher script
sudo nano /etc/NetworkManager/dispatcher.d/99-restart-ssh
#!/bin/bash
if [ "$1" = "eth0" ] && [ "$2" = "up" ]; then
systemctl restart sshd
fi
# Make executable
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-restart-ssh
After implementing either solution, test by:
- Changing the server's IP address
- Waiting 2 minutes (for DHCP lease renewals)
- Attempting SSH connection with new IP
You should now maintain persistent access without manual intervention. The solutions work because they either:
- Make SSH properly bind to all interfaces (systemd socket method)
- Automatically restart the service on network changes (NetworkManager method)
For maximum reliability, consider combining these solutions with:
# Configure sshd to use all available interfaces
sudo nano /etc/ssh/sshd_config
AddressFamily any
Remember that these solutions apply specifically to Ubuntu 18.04's default configuration. Newer versions may handle dynamic IP changes more gracefully.