1. Introduction

Since the exhaustion of IPv4 addresses, the IPv6 kernel module typically enables and configures IPv6 on network interfaces automatically. However, some scenarios work only when IPv6 is off.

In this tutorial, we explain how to disable IPv6 globally and on a given network interface. First, we look at a Linux pseudo-filesystem and its associated tool, both of which expose kernel settings. Next, we see how they can be leveraged to disable IPv6 for the whole system and for specific interfaces. Finally, we demonstrate how each setting works.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.

2. The /proc Pseudo-Filesystem and the sysctl Tool

Linux provides the /proc procfs pseudo-filesystem, a UNIX-remnant interface to internal kernel configuration:

$ ls --ignore '[0-9]*' /proc
acpi       diskstats      ioports      loadavg       pressure       thread-self
asound     dma            irq          locks         schedstat      timer_list
buddyinfo  driver         kallsyms     meminfo       self           tty
bus        dynamic_debug  kcore        misc          slabinfo       uptime
cgroups    execdomains    keys         modules       softirqs       version
cmdline    fb             key-users    mounts        stat           vmallocinfo
consoles   filesystems    kmsg         mtrr          swaps          vmstat
cpuinfo    fs             kpagecgroup  net           sys            zoneinfo
crypto     interrupts     kpagecount   pagetypeinfo  sysrq-trigger
devices    iomem          kpageflags   partitions    sysvipc

Here, we use –ignore ‘[0-9]*’ to exclude objects with numerical names, i.e., IDs of current processes, since such objects are directories that expose data about running instances by process ID (PID).

As we can see, besides them, there are many general categories of information. These are spread in a hierarchical structure of directories and files. Essentially, reading files from /proc extracts configuration parameters, while changing files in /proc modifies settings.

To organize these changes and make them easier to perform, many Linux distributions provide the sysctl utility. Let’s use it to see all available settings:

$ sysctl --all
[...]
user.max_user_namespaces = 7323
user.max_uts_namespaces = 7323
vm.admin_reserve_kbytes = 8192
vm.compact_unevictable_allowed = 1
vm.compaction_proactiveness = 20
vm.dirty_background_bytes = 0
[...]

The list is long but aims to replicate the structure of /proc. Of course, some settings are read-only.

Now, let’s read and change a setting directly and via sysctl:

$ cat /proc/sys/fs/file-max
9223372036854775807
$ printf 9223372036854775806 > /proc/sys/fs/file-max
$ sysctl fs.file-max
fs.file-max = 9223372036854775806
$ sysctl fs.file-max=9223372036854775807
fs.file-max = 9223372036854775807

First, we check the setting by displaying the contents of /proc/sys/fs/file-max with cat. Next, we use printf and stream redirection to write a new value in the file. After that, we verify the value of the setting with sysctl by passing its proper breadcrumb path. Lastly, we write a new value with =, which sysctl automatically echoes to confirm.

Finally, to persist settings between reboots, we can write them to /etc/sysctl.conf, again as key=values pairs, one per line. In this article, we’ll only use sysctl since it’s equivalent to direct editing of the files in /proc.

3. Disable IPv6 with sysctl

To disable IPv6, we can use sysctl and its net.ipv6.conf category. Let’s see how.

There are two general settings for disabling IPv6 via sysctl: net.ipv6.conf.all.disable_ipv6 and net.ipv6.conf.default.disable_ipv6. However, turned on by assigning 1 instead of 0, they prevent the system from configuring IPv6 on any network by default or manually:

$ sysctl net.ipv6.conf.all.disable_ipv6=1
$ sysctl net.ipv6.conf.default.disable_ipv6=1

To disable IPv6 on a particular interface, we can use another setting: net.ipv6.conf.IFACE.disable_ipv6, where IFACE is the name of the interface of interest. Importantly, to use IPv6, both the interface-specific and the system-wide disable settings have to be set to 0, while only one set to 1 disables it:

$ sysctl net.ipv6.conf.all.disable_ipv6=1
$ sysctl net.ipv6.conf.default.disable_ipv6=1
$ sysctl net.ipv6.conf.eth0.disable_ipv6=1

Now that we know the settings and their relations, we can verify their consequences.

4. Demonstration

Let’s see how configuring the kernel network options takes effect. To that end, we’ll poll or attempt to change our interface with ip before and after toggling them. Our target network interface will be eth0.

First, we check the current values of the IPv6 disabling settings:

$ sysctl net.ipv6.conf.all.disable_ipv6
net.ipv6.conf.all.disable_ipv6 = 0
$ sysctl net.ipv6.conf.default.disable_ipv6
net.ipv6.conf.default.disable_ipv6 = 0
$ sysctl net.ipv6.conf.eth0.disable_ipv6
net.ipv6.conf.eth0.disable_ipv6 = 0

We have left IPv6 enabled and allowed globally and on eth0. Next, we check the current network interface settings:

$ ip address show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:11:06:66:00:10 brd ff:ff:ff:ff:ff:ff
    inet 192.168.6.66/24 brd 192.168.6.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:0:666::1/64 scope global
       valid_lft forever preferred_lft forever

Indeed, we have an IPv6 address set as 2001:db8:0:666::1/64. After this, we disable IPv6 for the eth0 interface and again poll the network settings:

$ sysctl net.ipv6.conf.eth0.disable_ipv6=1
net.ipv6.conf.eth0.disable_ipv6 = 1
$ ip address show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:11:06:66:00:10 brd ff:ff:ff:ff:ff:ff
    inet 192.168.6.66/24 brd 192.168.6.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever

Now, the inet6 lines have disappeared, as expected. In fact, we can’t set an IPv6 address, even as a superuser:

# ip -6 addr add 2001:0db8:0:0666::1/64 dev eth0
RTNETLINK answers: Permission denied

Hence, we successfully disabled IPv6 for a given interface. Appending net.ipv6.conf.eth0.disable_ipv6=1 to /etc/sysctl.conf or a new file in the /etc/sysctl.d/ directory, we can make the setting permanent for that interface.

5. GRUB IPv6 Disable

While there is a way to turn IPv6 off via GRUB, it’s rarely the recommended one unless we want to save resources.

In essence, it comes down to appending ipv6.disable=1 to the GRUB_CMDLINE_LINUX variable in /etc/default/grub. Next, we only need to update the GRUB settings:

$ grub2-mkconfig -o /boot/grub2/grub.cfg

After a reboot, the IPv6 kernel module will be off, preventing us from changing any settings via sysctl or /proc. Because of this restriction, the method is usually not preferred.

6. Summary

In this article, we looked at the most common way to disable IPv6 on modern Linux distributions.

In conclusion, while IPv6 is required in some cases, it is easy and can be valuable to disable it.