Permission denied shows up when you read a file, run a script, and enter a directory, and it's three different problems wearing the same message. Here's how rwx permissions actually work, what the numbers in chmod mean, what chown is for, and why chmod -R 777 is a bad idea even when it "just works."

You edit a config, save a script, or try to open someone else's folder over SSH, and instead of a result you get the same line every time: Permission denied. It doesn't tell you what actually failed, whether it's the file, the directory, or the way you tried to run something, and that's exactly why it eats hours. In practice Permission denied on Ubuntu almost always means the same thing at the system level: your user is missing one specific bit of permission, read, write, or execute, on one specific object in the filesystem. Here's how to read ls -l output, what numbers like 644 and 755 in chmod actually mean, how chown differs from chmod, and why a site can throw 403 Forbidden even when the file's own permissions look completely correct.

Takeaways. Permission denied means your user is missing a specific permission, read, write, or execute, on a specific file or directory, not that the system is broken. chmod changes permissions: a numeric value like 644 or 755 breaks down into three groups of three bits, owner, group, everyone else, where r=4, w=2, x=1. On a file, x means "can be run as a program." On a directory it means something else entirely: "can be entered, and its contents reached by name." Confusing those two x's is the single most common beginner mistake here. chown changes owner and group (chown user:group file) and almost always needs sudo, even on your own files, unlike chmod, which only needs sudo on files you don't own. The classic 403 Forbidden case: permissions were set on the last folder only, while the web server can't traverse one of the parent directories further up the path. umask sets the permissions new files get at creation, usually 022 for root but 002 for a regular user on Ubuntu (user-private groups), which is why a fresh file created by a regular user usually lands at 664, not 644. And the big one: chmod -R 777 doesn't fix a permissions problem, it papers over the symptom by making everything writable by literally everyone. For anything with multiple users needing different access, setfacl beats "give everyone everything."

What Permission denied actually means, and why it's three different problems

Linux permissions aren't a single yes/no switch, they're three separate bits for three separate categories of user: read (r), write (w), and execute (x). Permission denied fires whenever the system is missing the specific bit needed for the specific action, and the exact wording tends to shift depending on what you were trying to do, which is a useful clue if you know what to look for.

  • Trying to read or edit a file's contents, but you lack access: cat: secret.env: Permission denied.
  • Trying to run a file directly as a program via ./, but it has no execute bit: -bash: ./deploy.sh: Permission denied.
  • Trying to enter a directory you don't have access into: -bash: cd: /root/private: Permission denied.
  • Trying to list a directory's contents that you can't get into: ls: cannot open directory 'private': Permission denied.

If a similarly worded error shows up not while working with a file on the server, but at the moment you're connecting over SSH itself, say Permission denied (publickey) when running ssh, that's a separate authentication issue, not a filesystem permission one. It's covered in the article on root login and SSH.

Reading ls -l output

Before changing anything, it helps to actually see what's set right now. ls with the -l flag prints a long-format listing that includes permissions, ownership, and size:

ls -l site.conf

What you'll see: a line like -rw-r--r-- 1 deploy deploy 1042 Sep 10 09:14 site.conf. Breaking it down:

  • the first character is the object type: - for a regular file, d for a directory, l for a symbolic link (a pointer file to another file);
  • the next nine characters are permissions, in three groups of three: rw- for the owner, r-- for the group, r-- for everyone else;
  • the number after that is the hard link count, safe to ignore for now;
  • then two names, the owning user and the group the file belongs to;
  • then size in bytes, modification date, and the filename.

For a directory the leading character reads d, not -, for example drwxr-xr-x 3 deploy deploy 4096 Sep 10 09:14 public. The difference between what x means on a file versus a directory is the central idea in the rest of this article.

chmod's numeric format: what 644, 755, and 700 actually mean

chmod (change mode) is the command that changes permissions on a file or directory. The three-digit numeric format is the fastest way to use it, once you understand where those digits come from. Each digit is a sum of three values: read (r) is worth 4, write (w) is worth 2, execute (x) is worth 1. The three digits, in order, apply to the owner, the group, and everyone else on the system.

chmod 644 site.conf

What you'll see afterward in ls -l: -rw-r--r--. Breaking down 644: the first 6 is 4+2, owner can read and write but not execute; the second 4 means the group can only read; the third 4 means everyone else can only read too. That's the standard set for an ordinary file, a config, an HTML page, an image.

Here's the part that's easy to miss: x carries a different meaning depending on whether you're looking at a file or a directory. On a file, x means "this can be run as a program." On a directory, x means something else entirely, "you can enter this directory and reach the files inside by name," sometimes called traverse or search permission. Without x on a directory, cd into it fails even if you have read (r) permission, because read on a directory only gets you the list of filenames through ls, not access to what's actually in them. That's exactly why working directories almost always carry 755 (owner gets everything, everyone else can enter and look but not write), never 644, a directory without x would be nearly useless, nobody could get into it.

Permission (number)

As rwx

What it means

Typical case

700

rwx------

owner gets everything, nobody else gets anything

a private directory like ~/.ssh, personal scripts

750

rwxr-x---

owner gets everything, group can enter and read, everyone else gets nothing

a directory shared within a team through group membership, closed to everyone else

755

rwxr-xr-x

owner gets everything, everyone else can enter/read/execute but not write

ordinary directories and executable scripts that need to be reachable by anyone

644

rw-r--r--

owner reads and writes, everyone else only reads

a standard file: a config, HTML, an image

640

rw-r-----

owner reads and writes, group only reads, everyone else gets nothing

a file with sensitive data readable by the owner and their group only

600

rw-------

owner only, and not even execute

secrets: .env files, private keys

777

rwxrwxrwx

literally everyone can read, write, and execute

almost never what you actually want, see the dedicated section below on why

chmod's letter format, for when numbers are overkill

The letter format doesn't touch all three groups at once, it changes only what you explicitly name, which is handy when you need to fix one specific bit without recomputing the whole number. u, g, o, a stand for user (owner), group, other, and all three at once; +, -, = mean add a permission, remove a permission, or set permissions to exactly what follows:

chmod u+x deploy.sh
chmod go-w site.conf
chmod a+r image.png

  • u+x adds execute for the owner only, leaves every other permission on the file untouched;
  • go-w removes write from the group and from everyone else, leaves the owner exactly as it was;
  • a+r adds read to all three categories at once.

Both formats do the same job in the end, numeric is faster to type, letter format reads more clearly in a script when only one thing is changing. There's no need to standardize on one, pick whichever fits the situation.

chown: who owns the file, and why it wants sudo

Every file on Linux carries two identity attributes beyond permissions, an owning user and a group, both visible in the third and fourth columns of ls -l output. chown (change owner) changes one or both:

sudo chown deploy:deploy site.conf

  • chown user:group file changes both owner and group at once;
  • chown user file, no colon, changes only the owner, leaving the group exactly as it was;
  • chown :group file changes only the group, leaving the owner as it was (there's also a dedicated chgrp command that does the same thing).

There's a fork here that trips people up: when sudo is required, and when it isn't. For chmod the rule is simple: your own file doesn't need sudo, changing permissions on something you own is allowed without elevated privileges; sudo only comes into play on files owned by someone else or by root, editing something under /etc/, say. chown is stricter, but only for the owner half: changing ownership almost always needs sudo, even on a file that's currently yours - otherwise an ordinary user could hand a file off to someone else, along with the disk-quota space it takes up. Changing the group (chown :group file or chgrp group file), though, needs no sudo as long as you're already a member of the target group - that's how you usually give www-data access without touching root: not by changing the owner, but by putting the file in a group the web server already belongs to.

The classic case: a site returns 403 Forbidden even though the file's own permissions look fine

One of the most common real cases: you set permissions on the final file, or the last folder in the site, everything looks correct, and the browser still shows 403 Forbidden. The cause is almost always that permissions got checked somewhere you didn't look. The web server runs as its own dedicated system user, usually www-data on Ubuntu and Debian, and reaching a file requires two things at once from that user: read access to the file itself, and traverse (x) permission on every parent directory along the way to it, not just the last one.

Picture the path /var/www/mysite/public/index.html. If /var/www/mysite is set to 700 and owned by your deploy user, while public/ and index.html itself are configured perfectly, www-data still gets refused, because it can't get past the very first directory in the chain. The permissions on the final file don't even matter in this situation, nothing ever reaches it.

namei with the -l flag checks the whole chain at once, walking the path segment by segment and showing permissions on each one:

namei -l /var/www/mysite/public/index.html

What to look for in the output: a line per path segment, and what you need is an x bit for group or other (depending on how access is set up) at every step - if any intermediate directory, including /var/www/mysite itself, has no x for anything but the owner, that's the 403. Without sudo the output just stops at the first directory you can't pass ("Permission denied" instead of the next line) - which is useful too: the point where it cuts off is the culprit, you don't even need to reach the end. The fix is adding traverse permission exactly where it's missing, without touching anything else:

sudo chmod o+x /var/www/mysite

That opens entry into the directory for everyone while leaving write access closed, which is usually enough, and safer than handing the whole site over to www-data as owner. In practice, 403 Forbidden after a permissions fix is almost never about the file itself, it's almost always a parent directory somebody forgot.

Forgot chmod +x: Permission denied on ./script.sh

Another common scenario: you just wrote or downloaded a script, try running it directly, and get shut down:

./deploy.sh

What you'll see: -bash: ./deploy.sh: Permission denied. The reason is straightforward, the file has no execute bit, and running it via ./ asks the system to execute the file directly as a program. The #!/usr/bin/env bash line at the top (the shebang, telling the system which interpreter should run the rest of the file) doesn't save you here, the system checks whether the file is executable at all before it ever looks at what's inside.

chmod +x deploy.sh
./deploy.sh

After chmod +x, the same command works. There's a fork worth knowing here: if instead of running it directly you call the script through an interpreter explicitly, bash deploy.sh rather than ./deploy.sh, the execute bit isn't needed at all. In that case the system never executes the file as its own program, bash opens and reads it line by line instead, and plain read permission is enough. If a tutorial has you run a script as bash script.sh instead of ./script.sh and there's no permissions error, that's not a typo in the instructions, it's a deliberate choice by whoever wrote it.

umask: why a new file isn't wide open by default

Permissions on new files and directories aren't pulled out of thin air, they come from umask, a value that clears the bits it names from a base permission set at the moment something is created (a mask, not subtraction - there's an important catch below). An ordinary file's base is 666 (read and write for everyone, no execute, since most files shouldn't be programs by default), a directory's base is 777 (a directory always needs x, or nobody could ever get into it). Check the current value:

umask

What you'll see: typically 0022 for root, but 0002 for a regular user on Ubuntu. The difference isn't random: Ubuntu enables USERGROUPS_ENAB in /etc/login.defs by default, and every regular user gets their own private group named after them - under that scheme, group permissions are deliberately kept equal to the owner's, so the mask is looser. In practice: a new file lands at 644 for root, 664 for a regular user (the group can write too); a directory lands at 755 for root, 775 for a regular user. Confirm it in practice:

touch newfile.txt && ls -l newfile.txt
mkdir newdir && ls -ld newdir

As a regular user on a stock Ubuntu image, the first command typically prints -rw-rw-r--, the second drwxrwxr-x - don't be surprised by the extra group w, that's 002 working as intended. The value isn't fixed in stone and changes for the current session, with something like umask 027. But here's the catch: "base minus umask" only happens to match bitwise clearing for 022 and 002 - it doesn't for most other values. 027, for instance, doesn't give 666−027=639 (there's no such mode): the mask 027 clears write for the group and read/write/execute for everyone else, and the real result is 640 for a file, 750 for a directory. If you need new files locked down from outsiders, verify the actual result with umask 027 && touch test && ls -l test rather than doing subtraction in your head.

What not to do: chmod -R 777 and recursion without thinking

The -R flag on chmod and chown applies a change recursively, to an entire tree of directories and files at once, useful when you genuinely need a bulk fix, and dangerous when it's typed without thinking. chmod -R 777 is the classic example: it does make an access error disappear, because it makes literally everything accessible to literally everyone, and that's precisely why it should be avoided, not just because it's "bad practice" with no explanation attached.

What it actually breaks:

  • files become writable not just by you but by any other user or process on the server, if a compromised script or someone else's account on a shared host gets in, it can now rewrite site content with zero additional barriers;
  • the distinction between files and directories vanishes, ordinary data like images and text files picks up an execute bit it doesn't need and gets nothing from, just confusion the next time someone audits permissions;
  • some programs refuse outright to work with permissions that open: ssh refuses a private key that's readable by the group or others (write isn't even required - read alone is enough to trigger it), and separately refuses the ~/.ssh directory if it's writable by the group or others - two different bits, checked in two different places, but both print some variant of "too open"/"bad permissions";
  • a recursive command run against the wrong path, a typo, an extra space near a /, changes permissions far beyond what you meant to touch, and against system directories that can leave the server in a broken state entirely.

The right move is fixing the specific spot missing a specific bit, not flattening the whole tree to one number. If you genuinely need to bulk-normalize permissions after moving files around, separate files from directories instead of assigning them identical permissions:

sudo find /var/www/mysite -type d -exec chmod 755 {} \;
sudo find /var/www/mysite -type f -exec chmod 644 {} \;

The first command sets 755 only on directories (they need x to be enterable), the second sets 644 only on files (they usually don't need execute at all). For more complex cases, several users needing genuinely different access levels to the same folder, say a deploy user needing write access alongside www-data needing only read, the classic owner/group/other triad sometimes just isn't enough. That's what ACLs (Access Control Lists) are for, a more flexible mechanism that grants permissions to a specific additional user or group on top of the usual three categories. The tooling for it usually needs installing separately on Ubuntu:

sudo apt install acl
sudo setfacl -m u:www-data:rX /var/www/mysite
getfacl /var/www/mysite

That's a topic for its own article, but as a direction for complex cases, keep it in mind: when the classic triad genuinely isn't enough, the next step is ACLs, not blanket 777.

What does Permission denied mean on Ubuntu?

It means your current user is missing a specific permission, read, write, or execute, on a specific file or directory. The exact cause depends on what you were doing, opening a file, running it directly with ./, or entering a directory each tend to produce a slightly different wording, but the underlying reason is the same.

How do I give a file permissions on Ubuntu with chmod?

Use chmod, either numerically (chmod 644 file, owner reads and writes, everyone else only reads) or with letters (chmod u+w file, add write for the owner). Changing permissions on your own files needs no sudo; files owned by another user or by root need sudo chmod.

What does chmod 755 mean, and how is it different from 644?

755 gives the owner full access (read, write, execute) and gives the group and everyone else read and execute but not write, the standard for directories and executable scripts. 644 doesn't grant execute to anyone at all, just read for everyone else and read-write for the owner, the standard for ordinary files like configs and HTML pages.

What's the actual difference between chown and chmod?

chmod changes what's allowed to happen to a file, read, write, execute permissions. chown changes who the file belongs to, its owner and group. chmod on your own file needs no sudo, but chown almost always does, because handing a file to a different owner is a privilege reserved for someone with admin access.

Why does my site return 403 Forbidden if the file's own permissions look fine?

Usually the problem isn't the file at all, it's one of the parent directories on the way to it, the web server (typically running as www-data) needs not just access to the file but traverse permission through every directory along the path, starting from the root. namei -l /path/to/file checks the whole chain at once and prints permissions for each segment.

Why does ./script.sh say Permission denied even though I just created the file?

Because the file has no execute bit, and running it via ./ requires the system to execute it directly as a program. The fix is chmod +x script.sh. If you instead run it through an interpreter explicitly, bash script.sh, the execute bit isn't needed at all, the file just gets read rather than executed directly.

Can I just run chmod -R 777 to make it work?

Technically, yes, the access error goes away, and so does every bit of protection along with it: files become writable by any user or process on the server, not just you. That's not a fix, it's masking the symptom with a wide-open side effect. The better move is finding the specific spot missing the specific bit, namei -l for the site case, for example, and fixing only that.

Takeaways

  • Permission denied means a specific permission bit, read, write, or execute, is missing on a specific file or directory, not that the system is broken.
  • chmod changes permissions: three digits for owner/group/other, where r=4, w=2, x=1; on a file x means "can be run," on a directory it means "can be entered and its contents reached by name," genuinely different things.
  • chown user:group file changes owner and group; it almost always needs sudo, even on your own files, unlike chmod, which only needs it on files you don't own.
  • 403 Forbidden with correct file permissions almost always means the web server can't traverse a parent directory somewhere, check the whole chain with namei -l, not just the last folder.
  • Permission denied on ./script.sh usually means "forgot chmod +x", but it's not needed if the script is run explicitly through bash script.sh.
  • umask determines the permissions new files and directories get - 022 for root (file 644), but usually 002 for a regular user on Ubuntu (file 664) - and it's a bit-clearing mask, not subtraction.
  • chmod -R 777 doesn't solve a permissions problem, it opens everything to everyone on the server, for anything with multiple users needing different access, use setfacl instead of "give everyone everything."

What's next

If your actual problem is a command that isn't found at all rather than a permissions error, that's a different story covered in command not found on Ubuntu. If Permission denied shows up not while working with files but right at login, through su, sudo, or a direct SSH connection as root, see the guide to root login on Ubuntu. And for a broader list of common issues on a fresh Linux server, there's the common Linux server problems hub.