What is noexec Option in Linux /etc/fstab and how it works

Have you ever mounted a filesystem and wondered why a perfectly executable script suddenly refuses to run?

For example:

$ ls -l /data/test.sh
-rwxr-xr-x 1 user user 32 Sep 20 10:20 /data/test.sh

$ /data/test.sh
-bash: /data/test.sh: Permission denied

The file has the execute bit.

The owner is correct.

The script itself looks fine.

So why does Linux refuse to run it?

One possible reason is the filesystem was mounted with:

noexec

You may have seen it in /etc/fstab:

/dev/sdb1  /data  xfs  defaults,noexec  0 0

At first glance, noexec sounds simple: “don’t execute files from this filesystem.”

That’s roughly correct, but there is more to it.

See also: Mastering the Linux Command Line — Your Complete Free Training Guide

Understanding what noexec actually does—and what it doesn’t do—is important when you’re hardening Linux servers, mounting shared storage, working with temporary directories, or troubleshooting a mysterious Permission denied.

Let’s look at it with real examples.


What Does noexec Mean?

noexec is a filesystem mount option.

When a filesystem is mounted with:

noexec

Linux prevents programs located on that filesystem from being directly executed through the normal executable-loading mechanism.

For example:

mount -o noexec /dev/sdb1 /data

Now /data is mounted with the noexec option.

If you have:

/data/hello

and it is an executable binary:

$ ls -l /data/hello
-rwxr-xr-x 1 user user 16784 Sep 20 10:30 /data/hello

you may still get:

$ /data/hello
-bash: /data/hello: Permission denied

The important point is:

The file’s execute permission is not enough.

The filesystem itself says:

Don’t execute programs directly from here.


A Simple Example

Let’s create a small shell script.

mkdir /tmp/noexec-test

Create the script:

cat > /tmp/noexec-test/hello.sh <<'EOF'
#!/bin/bash
echo "Hello from the script"
EOF

Make it executable:

chmod +x /tmp/noexec-test/hello.sh

Normally:

/tmp/noexec-test/hello.sh

prints:

Hello from the script

Now let’s put the directory on a noexec filesystem.

For example, if /data is mounted with noexec:

cp /tmp/noexec-test/hello.sh /data/

Then:

chmod +x /data/hello.sh

Check the permissions:

ls -l /data/hello.sh

You might see:

-rwxr-xr-x 1 user user 35 Sep 20 10:30 /data/hello.sh

Try to run it:

/data/hello.sh

You may get:

-bash: /data/hello.sh: Permission denied

This is the part that often confuses people.

The file has:

x

permission.

But the filesystem has:

noexec

The filesystem-level restriction prevents direct execution.


Check Whether a Filesystem Uses noexec

Before troubleshooting, check the mount options.

The easiest command is:

findmnt /data

For example:

TARGET SOURCE    FSTYPE OPTIONS
/data  /dev/sdb1 xfs    rw,relatime,noexec

There it is:

noexec

You can also use:

mount | grep ' /data '

Example:

/dev/sdb1 on /data type xfs (rw,relatime,noexec)

Another useful command is:

findmnt -no OPTIONS /data

which might return:

rw,relatime,noexec

If you’re troubleshooting a particular file, findmnt is often the fastest way to determine which filesystem contains it:

findmnt /data/hello.sh

noexec in /etc/fstab

On a server, noexec is often configured in /etc/fstab.

For example:

/dev/sdb1  /data  xfs  defaults,noexec  0 0

The important part is:

defaults,noexec

The next time the system mounts /data, the filesystem will use the noexec option.

You can see the current configuration with:

grep -v '^#' /etc/fstab

For example:

/dev/mapper/vg_data-lv_data  /data  xfs  defaults,noexec  0 0

After changing /etc/fstab, don’t immediately reboot just to test it.

You can usually remount the filesystem:

mount -o remount /data

Then verify:

findmnt /data

You should see:

noexec

in the mount options.


What Happens When Linux Executes a File?

To understand noexec, it’s useful to look at what happens when you type:

/data/hello

Your shell eventually asks the Linux kernel to execute the file.

At the system-call level, this involves the execve() family of system calls.

A simplified view looks like this:

Shell
  |
  |  execute /data/hello
  v
execve()
  |
  v
Linux kernel
  |
  +--> Check file permissions
  |
  +--> Check filesystem mount flags
  |
  +--> Load executable
  |
  v
Program starts

With noexec, the filesystem’s mount flags tell the kernel that direct execution isn’t allowed from that filesystem.

So even if:

-rwxr-xr-x

says the file is executable, the mount policy can still prevent execution.


noexec Does Not Remove the Execute Bit

This distinction is important.

Suppose:

chmod +x /data/test.sh

Then:

ls -l /data/test.sh

shows:

-rwxr-xr-x

Mounting the filesystem with:

noexec

doesn’t change the file’s permissions.

You can still see:

x

in the permission string.

The restriction exists at the filesystem mount level.

Think about it like this:

File permissions
      +
Filesystem mount policy
      =
Whether direct execution is allowed

Both layers matter.


noexec Is Not the Same as chmod -x

These two approaches look similar but work differently.

Remove execute permission:

chmod -x /data/test.sh

Now:

ls -l /data/test.sh

might show:

-rw-r--r--

The file itself no longer has execute permission.

With noexec, however:

ls -l /data/test.sh

can still show:

-rwxr-xr-x

while:

/data/test.sh

fails.

The difference is:

chmod -x
    |
    +--> File-level permission

noexec
    |
    +--> Filesystem-level execution restriction

What About Shell Scripts?

This is where things get interesting.

Suppose /data is mounted with noexec.

You have:

/data/test.sh

with:

#!/bin/bash
echo "Hello"

Direct execution:

/data/test.sh

may fail:

Permission denied

But try:

bash /data/test.sh

You may get:

Hello

Why?

Because you’re not asking the kernel to execute /data/test.sh as the program.

You’re executing:

/bin/bash

and telling Bash to read /data/test.sh as input.

The flow is different:

/data/test.sh
     |
     X
direct execution blocked by noexec

versus:

/bin/bash
     |
     +--> read /data/test.sh
     |
     +--> interpret commands

This is one of the most important things to understand about noexec.

noexec does not mean “files on this filesystem cannot be read or interpreted.”

It primarily restricts direct execution.


Example: noexec Does Not Stop Bash From Reading a Script

Suppose:

findmnt /data

returns:

/data  /dev/sdb1  xfs  rw,relatime,noexec

And:

cat /data/test.sh

works:

#!/bin/bash
echo "Hello"

Then:

/data/test.sh

fails:

Permission denied

But:

bash /data/test.sh

works.

This is expected.

The filesystem is not saying:

“You cannot read this file.”

It’s saying:

“Don’t directly execute programs from this filesystem.”


What About Python?

The same concept applies to interpreted languages.

Suppose you have:

/data/test.py

with:

#!/usr/bin/python3

print("Hello")

Direct execution:

/data/test.py

may fail when /data is mounted with noexec.

But:

python3 /data/test.py

may work.

Again:

python3
   |
   +--> reads /data/test.py

The Python interpreter itself is being executed from another filesystem.

The script is simply being read as data.


Does noexec Stop ELF Binaries?

Yes, this is one of the primary purposes of noexec.

Suppose:

/data/myprogram

is an ELF executable:

file /data/myprogram

might return:

/data/myprogram: ELF 64-bit LSB pie executable, x86-64, ...

And:

ls -l /data/myprogram

shows:

-rwxr-xr-x

If /data is mounted with noexec:

/data/myprogram

can fail with:

Permission denied

even though the binary itself is executable.


Copy the Binary Somewhere Else

Here’s a simple demonstration.

Suppose:

/data/hello

doesn’t run:

/data/hello

returns:

Permission denied

Copy it to /tmp:

cp /data/hello /tmp/hello

Make sure /tmp isn’t also mounted with noexec:

findmnt /tmp

Then:

/tmp/hello

may run successfully.

Nothing changed about the binary.

You changed the filesystem from which you’re executing it.

That’s a very useful troubleshooting technique.


noexec and /tmp

One common place where administrators use noexec is /tmp.

You may see something like:

tmpfs /tmp tmpfs defaults,nodev,nosuid,noexec 0 0

The three options are often discussed together:

nodev
nosuid
noexec

They solve different problems.

nodev

Prevents device files from being interpreted as devices on the filesystem.

nosuid

Disables the effect of set-user-ID and set-group-ID bits.

noexec

Prevents direct execution of programs from the filesystem.

So:

nodev
  -> device files

nosuid
  -> SUID/SGID behavior

noexec
  -> direct program execution

They are separate controls.


A Practical /tmp Example

Suppose /tmp is mounted as:

tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime)

Someone downloads a binary:

curl -o /tmp/tool https://example.com/tool

Then:

chmod +x /tmp/tool

and:

/tmp/tool

fails:

Permission denied

But if they copy it to a filesystem mounted without noexec:

cp /tmp/tool ~/tool
chmod +x ~/tool
~/tool

it may run.

Again, the important variable is the filesystem mount option.


noexec Does Not Mean “No Code Can Run”

This is perhaps the biggest misconception.

Suppose:

/home/user

is mounted with noexec.

It does not necessarily mean the user cannot run programs.

For example:

/usr/bin/bash

can still run if /usr is mounted with normal execution permissions.

Then Bash can read:

/home/user/test.sh

as a script.

Similarly:

python3 /home/user/test.py

can still work.

The same applies to programs that load libraries or data from other filesystems, although there are additional details depending on how the files are used.


How to Find Which Mount Contains a File

When you see:

Permission denied

for an executable, don’t immediately run:

chmod +x

First check the filesystem:

findmnt /path/to/file

For example:

findmnt /opt/myapp/bin/server

You might get:

TARGET SOURCE    FSTYPE OPTIONS
/opt   /dev/sdc1 xfs    rw,relatime,noexec

Now you know why the binary cannot execute.

You can also check the mount point with:

df -T /opt/myapp/bin/server

and:

mount | grep ' /opt '

A Realistic Troubleshooting Example

Imagine an application deployment fails.

The deployment system reports:

Permission denied

You check:

ls -l /opt/app/bin/start.sh

and see:

-rwxr-xr-x 1 app app 1024 Sep 20 10:20 /opt/app/bin/start.sh

At first glance, everything looks correct.

You try:

/opt/app/bin/start.sh

and get:

Permission denied

Don’t immediately change permissions.

Check:

findmnt /opt

Suppose the result is:

TARGET SOURCE    FSTYPE OPTIONS
/opt   /dev/sdc1 xfs    rw,relatime,noexec

Now the problem is obvious.

The file has execute permission.

But /opt is mounted with noexec.


How to Temporarily Remove noexec

If you have the appropriate administrative access and need to test whether noexec is the cause, you can remount the filesystem.

For example:

sudo mount -o remount,exec /opt

Then verify:

findmnt /opt

Try the program again:

/opt/app/bin/start.sh

If it now works, you’ve confirmed that noexec was involved.

However, don’t treat this as a permanent fix without understanding why noexec was configured.

On production systems, mount options are often part of the security baseline.


Changing /etc/fstab

If you have:

/dev/sdc1 /opt xfs defaults,noexec 0 0

and you remove noexec:

/dev/sdc1 /opt xfs defaults 0 0

you can apply the change with:

sudo mount -o remount /opt

Then check:

findmnt /opt

But be careful.

There is a difference between:

mount -o remount,exec /opt

and permanently changing:

/etc/fstab

The first changes the current mount.

The second changes what happens after the next mount or reboot.


noexec and Container Environments

noexec can also become relevant when working with containers.

For example, an application may mount a host directory into a container:

/data:/app/data

If the underlying filesystem has noexec, applications may encounter execution problems depending on where binaries are stored and how they are launched.

This can be especially confusing because the container configuration may look correct.

If an application says:

permission denied

while trying to execute a binary, inspect the host filesystem and mount options as well as the container configuration.

The important lesson is:

A container does not make filesystem mount flags disappear.

The exact behavior depends on the container runtime, mount namespace, and how the filesystem is exposed to the container.


noexec and Network Filesystems

You may also encounter noexec with NFS or other shared filesystems.

For example:

server:/export/tools /tools nfs defaults,noexec 0 0

This can be useful when /tools is intended primarily as shared data storage and users shouldn’t directly execute arbitrary programs from it.

But it can also break applications that expect to execute binaries or scripts directly from the mount.

If an application works on local storage but fails after moving files to NFS, check:

findmnt /tools

and:

mount | grep /tools

before assuming the application itself is broken.


noexec vs nosuid vs nodev

These three options often appear together:

nodev,nosuid,noexec

But they should not be treated as interchangeable.

OptionMain purpose
noexecPrevent direct execution of programs
nosuidIgnore SUID/SGID privilege changes
nodevDon’t interpret device files as devices

For example:

/dev/sdb1 /data xfs defaults,nodev,nosuid,noexec 0 0

creates a more restrictive mount.

This can make sense for filesystems used for uploads, temporary data, shared files, or other content that should not normally contain executable programs.


How to Test noexec Yourself

If you want to understand noexec rather than just memorize it, build a small test environment.

Create a filesystem image:

dd if=/dev/zero of=/tmp/test.img bs=1M count=100

Create a filesystem:

mkfs.ext4 /tmp/test.img

Create a mount point:

sudo mkdir -p /mnt/noexec-test

Mount it:

sudo mount -o loop,noexec /tmp/test.img /mnt/noexec-test

Verify:

findmnt /mnt/noexec-test

You should see something similar to:

TARGET            SOURCE      FSTYPE OPTIONS
/mnt/noexec-test  /dev/loop0  ext4   rw,relatime,noexec

Now create a script:

cat > /mnt/noexec-test/test.sh <<'EOF'
#!/bin/bash
echo "noexec test"
EOF

Make it executable:

chmod +x /mnt/noexec-test/test.sh

Try:

/mnt/noexec-test/test.sh

You should see:

Permission denied

Now try:

bash /mnt/noexec-test/test.sh

You should see:

noexec test

This small experiment demonstrates the difference better than a long definition.


When Should You Use noexec?

noexec makes sense when a filesystem is primarily intended for data rather than executable programs.

Common examples include:

  • Temporary storage
  • User-uploaded files
  • Shared data directories
  • Certain backup locations
  • Application data directories
  • Some removable storage
  • Specific network filesystems

For example:

/dev/sdb1 /data xfs defaults,nodev,nosuid,noexec 0 0

can make sense if /data is strictly intended for application data.

But don’t blindly add noexec to every filesystem.

Some applications legitimately need to execute files from particular locations.


The Bottom Line

The easiest way to remember noexec is:

noexec is a filesystem mount restriction, not a file permission.

A file can have:

-rwxr-xr-x

and still fail to execute when its filesystem is mounted with:

noexec

Check it with:

findmnt /path/to/file

For example:

findmnt /data/test.sh

If you see:

noexec

you’ve found an important clue.

And remember the difference between:

/data/test.sh

and:

bash /data/test.sh

The first asks Linux to execute the file directly.

The second executes Bash and asks Bash to read the file.

That’s why noexec can block the first command while allowing the second.

Once you understand that distinction, many of those mysterious:

Permission denied

errors become much easier to troubleshoot.

Avatar photo
David Cao

David is a Cloud & DevOps Enthusiast. He has years of experience as a Linux engineer. He had working experience in AMD, EMC. He likes Linux, Python, bash, and more. He is a technical blogger and a Software Engineer. He enjoys sharing his learning and contributing to open-source.

Articles: 731

Leave a Reply

Your email address will not be published. Required fields are marked *