Linux & Systems

Disabling IPv6 on a Linux Server, and When Not To

Decide whether disabling IPv6 actually solves your problem, then do it with a sysctl drop-in or the ipv6.disable=1 kernel parameter on Debian, Ubuntu, Rocky Linux, or AlmaLinux, verify the result, and roll it back.

8 min readUpdated Tuesday, Oct 13, 2026
linuxipv6networkingdebianubuntu

An aerial view of a pale road splitting in two through a dense green conifer forest

Most VPSes come with two kinds of address: the familiar IPv4 one and an IPv6 one, which is usually set up whether you asked for it or not. Disabling IPv6 is a common reflex when something network-shaped misbehaves. Sometimes it’s the right fix. Often it just moves the problem to a service that expected ::1 to exist.

This note covers how to tell which case you’re in, the two ways to switch IPv6 off (a runtime sysctl, or a kernel boot parameter), what each one breaks, and how to undo them on Debian, Ubuntu, and the RHEL family.

First, look before you switch anything off

These commands only read:

ip -6 addr show scope global
ip -6 route show default
ss -tlnp

The first two tell you whether the server has a public IPv6 address and a route to use it. Then test whether that route actually goes anywhere:

ping -6 -c 3 2606:4700:4700::1111

That’s Cloudflare’s public DNS resolver. Replies mean IPv6 works and probably isn’t your problem. If you have an address and a route but the pings time out, you’ve found the classic bad case: the server thinks it has IPv6, so software tries it first and waits before falling back to IPv4. In practice, apt or curl sits on an IPv6 address for a while before finally connecting.

Good reasons to disable IPv6:

  • The provider assigns IPv6 but the path is broken, and fixing it (or asking them to) isn’t an option.
  • Your firewall only has IPv4 rules, services listen on IPv6 too, and you aren’t going to write the IPv6 rules. In ss -tlnp, local addresses like [::]:80 or *:22 are IPv6 or dual-stack listeners. ufw writes IPv6 rules by default; hand-written iptables scripts often don’t.
  • An application or policy explicitly requires it.

Not a great reason: “less attack surface”, when nothing listens on IPv6 that isn’t already firewalled. In that case there’s no open door to close.

If you just want IPv4 to be tried first, there’s a gentler option. glibc’s /etc/gai.conf ships with this exact line commented out:

#precedence ::ffff:0:0/96  100

Uncomment it and programs that use the normal resolver (getaddrinfo()) will sort IPv4 results ahead of IPv6, with IPv6 still available. To see the effect, compare getent ahosts deb.debian.org before and after. For apt alone, Acquire::ForceIPv4 "true"; in a file under /etc/apt/apt.conf.d/ does the same job.

Option 1: a sysctl drop-in

This tells the kernel to stop using IPv6 on network interfaces. The IPv6 code stays loaded, the change takes effect immediately, and no reboot is needed.

sudo tee /etc/sysctl.d/90-disable-ipv6.conf >/dev/null <<'EOF'
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
sudo sysctl -p /etc/sysctl.d/90-disable-ipv6.conf

The three keys work like this. all applies to every interface that already exists. default applies to interfaces created later, such as Docker bridges or a WireGuard tunnel. lo is the loopback interface, which all has already covered, but naming it makes the intent obvious.

sysctl -p echoes the three lines back when they apply. Now check:

ip -6 addr

No output means no interface has an IPv6 address left. Under the hood, the kernel deletes every IPv6 address and route on an interface the moment its disable_ipv6 flips to 1.

Why a file in /etc/sysctl.d/ and not a line in /etc/sysctl.conf? In Debian 13, systemd-sysctl no longer reads /etc/sysctl.conf at all. A line added there would work when you run sysctl -p by hand and then quietly do nothing at the next boot. Drop-ins in /etc/sysctl.d/ work on Debian, Ubuntu, and the RHEL family alike, and sudo sysctl --system reloads all of them without a reboot.

Keep loopback, skip most of the breakage

This is the variant worth reaching for first. Change the last line to 0:

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 0

Order matters here. The all line rewrites every interface, lo included, so the lo line has to come after it. Apply it again and ip -6 addr shows just one survivor:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host proto kernel_lo
       valid_lft forever preferred_lft forever

The server has no IPv6 on the network, but ::1 still exists for local programs. That matters because of what happens without it.

What the sysctl method breaks

  • Programs can still open IPv6 sockets and bind to ::, the IPv6 wildcard address. Binding to ::1 specifically fails with Cannot assign requested address once loopback has no IPv6 address.
  • localhost usually resolves to ::1 as well as 127.0.0.1. Run getent ahosts localhost to see the order on your system. A service told to listen on or connect to “localhost” can pick ::1 and fail.
  • sshd’s X11 forwarding binds to the loopback addresses. With ::1 gone, it can fail to set up the display. If you use it, add AddressFamily inet to an sshd drop-in, then run sshd -t and reload.
  • Anything running as root can flip it back at runtime. On the RHEL family, NetworkManager manages IPv6 per connection, so use the nmcli method below there.

Option 2: the kernel parameter

ipv6.disable=1 on the kernel command line goes further. In the kernel documentation’s own words, no IPv6 addresses will be added to interfaces, and it will not be possible to open an IPv6 socket at all. Turning it back on requires a reboot.

That stricter rule breaks different things. Software that asks for an IPv6 socket now gets an error, and some programs treat that as fatal. nginx is the usual example. Debian’s default site contains listen [::]:80 default_server;, and nginx will refuse to start with an error along the lines of socket() [::]:80 failed (97: Address family not supported by protocol). sshd is more relaxed and simply skips its IPv6 listener.

Before rebooting, find anything configured to use IPv6 explicitly:

ss -tlnp | grep -E '\[::|\*:'
grep -rn '\[::' /etc/nginx/ 2>/dev/null

Remove or comment out those listen [::]… lines first, and run nginx -t before you reload. Test Nginx Configuration Before Reload covers that habit. If you created the Option 1 file, remove it too. Its keys live under /proc/sys/net/ipv6, which won’t exist anymore.

On Debian and Ubuntu, add the parameter with a GRUB drop-in instead of editing /etc/default/grub:

sudo mkdir -p /etc/default/grub.d
echo 'GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX ipv6.disable=1"' | sudo tee /etc/default/grub.d/90-disable-ipv6.cfg
sudo update-grub

grub-mkconfig (which update-grub runs) reads /etc/default/grub first and then every /etc/default/grub.d/*.cfg in order. Your file appends to whatever is already set instead of replacing it. Among the output, you should see:

Sourcing file `/etc/default/grub.d/90-disable-ipv6.cfg'

This assumes the server boots through GRUB, which most Debian and Ubuntu VPS images do. If update-grub doesn’t exist, check how your provider boots the image before going further.

On Rocky Linux, AlmaLinux, and RHEL itself, use grubby, which writes the argument into every installed kernel’s boot entry:

sudo grubby --update-kernel=ALL --args="ipv6.disable=1"
sudo grubby --info=ALL | grep '^args'

Kernels installed later inherit the arguments from the current one. Now reboot, with the provider console open in a browser tab in case something you depend on was IPv6-only, and check:

cat /proc/cmdline          # should include ipv6.disable=1
ls /proc/sys/net/ipv6      # No such file or directory
ip -6 addr                 # no output
sudo dmesg | grep IPv6     # IPv6: Loaded, but administratively disabled, reboot required to enable
systemctl --failed         # anything that tripped over the change

If systemctl --failed lists a unit, Reading a Failed systemd Service is the next stop. Look for “Address family not supported” in its logs.

Rocky Linux and AlmaLinux: ask NetworkManager

CentOS Linux, which the original version of this note covered, is end-of-life. CentOS Linux 8 ended on December 31, 2021, and CentOS Linux 7 on June 30, 2024. On servers, its place is commonly taken by Rocky Linux or AlmaLinux, both rebuilds of RHEL, so Red Hat’s documentation applies to them.

Red Hat documents disabling IPv6 per connection through NetworkManager, which then sets the matching sysctl for that interface:

nmcli connection show
sudo nmcli connection modify <connection-name> ipv6.method disabled
sudo nmcli connection up <connection-name>

connection up re-activates the connection, which can briefly interrupt traffic on it. On a remote server, keep the console nearby. To verify, using the device name from the first command:

ip address show eth0                           # no inet6 lines
cat /proc/sys/net/ipv6/conf/eth0/disable_ipv6  # 1

If you use the kernel parameter on one of these systems, set ipv6.method disabled on the connection as well. That way NetworkManager isn’t trying to configure a protocol the kernel has already switched off.

Rolling back

Sysctl drop-in: delete the file and turn the flags back off.

sudo rm /etc/sysctl.d/90-disable-ipv6.conf
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0 net.ipv6.conf.default.disable_ipv6=0 net.ipv6.conf.lo.disable_ipv6=0

Link-local addresses come back right away, and router-advertised addresses return once the router answers. Statically configured IPv6 addresses were deleted when you disabled IPv6, so they need your network configuration reapplied (sudo netplan apply on Ubuntu, nmcli connection up on the RHEL family). Alternatively, reboot, which is boring and reliable.

Kernel parameter: remove it and reboot.

# Debian / Ubuntu
sudo rm /etc/default/grub.d/90-disable-ipv6.cfg && sudo update-grub

# Rocky / AlmaLinux / RHEL
sudo grubby --update-kernel=ALL --remove-args="ipv6.disable=1"

NetworkManager: sudo nmcli connection modify <connection-name> ipv6.method auto, then bring the connection up again.

Then put back any listen [::] lines you removed, if you want those services reachable over IPv6 again.

References

Cover photo: Unsplash.