Alright, let’s talk about SELinux modes, because this is where most people’s relationship with it goes sideways. You’ve probably already felt the pain: something isn’t working, you throw setenforce 0 at the problem like a magical incantation, and it starts working. Congratulations, you’ve just entered the “please don’t hurt me” mode of SELinux. Let’s demystify that magic and understand what you’re actually doing when you run that command.

SELinux has three fundamental states of being: Enforcing, Permissive, and Disabled. Think of them not as a simple on/off switch, but as a spectrum of how much of a hassle it’s going to be for you today.

Enforcing: The Bouncer with a Clipboard

This is the mode SELinux is supposed to run in. In enforcing mode, the policy is fully applied. Every single action by every subject (a process) on every object (a file, a port, a socket) is checked against the massive rulebook we call policy. If the action is allowed, it proceeds silently. If it’s not, it gets blocked hard, and a detailed audit message is generated.

This is the goal. This is what keeps your system secure. The kernel is the bouncer, the policy is the guest list, and any process trying to do something it shouldn’t gets shown the door immediately.

You can verify you’re in this mode with:

getenforce
# Enforcing

And if you need to put it there (assuming it’s not disabled, more on that later):

sudo setenforce 1

Permissive: The Bouncer Taking Notes

Permissive mode is SELinux’s training wheels mode, or more accurately, its debugging mode. In this state, the policy is still loaded and every single access check is performed exactly as it would be in enforcing mode. The crucial difference? It doesn’t actually deny anything.

It logs the denial (AVC message) as if it had blocked the action, but then it lets the action proceed anyway. This is phenomenally useful. It allows you to test a new application, a new policy, or just figure out what the hell is going on without actually breaking your system’s functionality.

This is why you run setenforce 0 when something’s broken—it makes the errors stop while letting you see why they were happening in the logs. You’re essentially telling SELinux, “Yell at me, but don’t get in my way.”

Switch to it with:

sudo setenforce 0
getenforce
# Permissive

The key insight here is that permissive and enforcing are runtime states that can be flipped instantly. They both require the SELinux policy to be loaded into the kernel. Which brings us to the third mode, the one that is fundamentally different.

Disabled: The Absent Bouncer

This one is deceptively named. Disabled doesn’t mean “sleeping” or “not enforcing.” It means not there. When you set SELinux to disabled in its config file and reboot, the kernel doesn’t even load the SELinux security module. The hooks aren’t there. It’s not logging, it’s not checking, it’s not doing a damn thing. It’s as if you compiled your kernel without SELinux support.

This is a crucial distinction. You can’t switch between disabled and permissive/enforcing with a simple setenforce command. Moving to or from disabled requires a reboot because it changes the fundamental way the kernel boots.

This is the most important pitfall to avoid: If you ever boot with SELinux disabled, all of the files on your system lose their SELinux labels because the extended attributes that store them can’t be checked. When you then try to switch back to enforcing mode, you have a system full of unlabeled files, which is an utter disaster. The system will often fail to boot entirely. The only way to fix it is to relabel the entire filesystem (touch /.autorelabel and reboot), which is a slow, painful process.

The best practice? Never use disabled. Seriously. Just don’t. If you’re tempted to disable it, use permissive instead. It gives you the operational freedom without creating a labeling catastrophe later. You manage the primary boot state in /etc/selinux/config:

sudo cat /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of these three values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

Change that SELINUX= line to what you want, but remember: enforcing for production, permissive for debugging, and disabled only if you enjoy causing yourself future pain.