Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

To serve files from Linux, the web-server process needs three things: the right identity, read access to each file it serves, and search (traverse) access to every directory above those files. Give it exactly that, and keep deployed code and static content unwritable by the same process. chown decides which user and group a file belongs to; chmod decides what those classes may do. Most permission failures on a web server come from getting one of these two wrong, or from a parent directory that quietly blocks the path.

This guide walks through the order that works in practice: identify the running identity, inspect the full path, change ownership, change modes narrowly, separate writable runtime data, check how new files are created, and verify the result as the service account.

Start with the identity that actually serves requests

Permissions only make sense relative to a specific user and group. On a web server, the account that matters is the one handling requests, not the one that installed the package or started the service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For NGINX, the master process reads and evaluates the configuration and manages worker processes, and the workers are the processes that handle requests. The worker identity is set by the user directive in the main context of nginx.conf, when that directive is present. Package defaults differ between distributions, so do not assume a name such as www-data or nginx without checking.

Confirm the running worker identity and the configured document root:

  1. Check the configuration for the worker user: grep -RInE '^s*users' /etc/nginx/
  2. Check the live worker processes: ps -eo user,group,pid,args | grep '[n]ginx'. Worker lines show the account that is actually serving requests.
  3. Find the document root and any write locations: grep -RInE '^s*(root|alias|client_body_temp_path|proxy_temp_path|fastcgi_temp_path)' /etc/nginx/

Apache HTTP Server follows the same principle with its own configuration directives, and PHP-FPM, Node.js, Python application servers and containers each run under their own identity. Whatever the stack, record the identity first. Every later chown or chmod decision depends on it.

Inspect the whole path before changing anything

A file can have correct permissions and still be unreachable. Linux checks each directory in the path from the top down, so a single parent directory without search permission for the web-server identity blocks every file beneath it.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use these commands to see the full chain:

  • namei -l /var/www/example/public/index.html lists each path component with its owner, group and mode. This is the fastest way to find the component that blocks access. namei comes from util-linux on most distributions.
  • stat -c '%U:%G %a %n' /var/www/example/public/index.html prints the owner, group, octal mode and name for one object.
  • getfacl /var/www/example/public shows whether extended ACL entries exist. Some systems return no ACL output when none are set, and getfacl is provided by the acl package where it is not installed by default.

Record the output before you change anything. It becomes your baseline if you need to roll back.

Choose ownership deliberately with chown

chown changes the owner, the group, or both. The syntax chown owner:group path sets both; chown :group path changes only the group. GNU chown accepts a group after a colon, and -R applies the change to every descendant.

A widely used arrangement separates deployment from serving:

  • The owner is a deployment or administrator account. It can update files and directories.
  • The group is a controlled group that includes the web-server identity. It grants read and, for directories, traversal.
  • The other class, meaning every other local user, gets no access unless you have a reason to grant it.

Example, assuming the account deploy and the group www-data both exist on your host:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

sudo chown deploy:www-data /var/www/example/public/index.html

Use chown only when ownership or group membership is the actual problem. Changing the owner of a tree to the web-server identity lets that process rewrite files it should only read, which undoes the separation you are trying to create. Ownership changes also require sufficient privilege; on most systems that means sudo or root.

Before any recursive ownership change, list the tree and check for symbolic links:

find /var/www/example -xdev -type l -print

Symlinks can point outside the intended tree, so review what they reach before you broaden a change.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set modes narrowly with chmod

chmod changes the permission bits. Octal and symbolic forms both work. The symbolic form is safer for targeted changes because it changes only the classes you name and leaves others alone.

Symbolic changes

  • chmod g+x /var/www/example/public adds group traversal to one directory without touching owner or other bits.
  • chmod u=rw,g=r,o= /var/www/example/config.env sets the owner to read and write, the group to read, and removes all access for others, leaving no unlisted bits to guess about.

On a directory, the execute bit means search (traverse). Without it, a process cannot reach names inside the directory, even if it can read the directory listing.

Octal modes and what they expose

The table below shows common octal modes. These are examples of the trade-off, not universal prescriptions.

Mode Owner Group Others Typical use Caveat
640 read, write read none Files the web server reads through group membership, such as configuration with restricted access The group must be the one the server identity belongs to, or the server cannot read the file
644 read, write read read Static files readable by everyone Any local user can read the file, which is often too broad on shared hosts
750 read, write, search read, search none Directories reachable only by owner and group Requires the server identity to be in the group
755 read, write, search read, search read, search Public static directories Lets every local user list and traverse the directory

The tradeoff is clear: 644 and 755 are convenient on single-purpose servers, but on a shared machine they expose content to all local users. When local confidentiality matters, use a group-specific mode such as 750 and 640, or ACL entries that name the server identity directly.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Modes to avoid

  • chmod -R 777 grants write access to every user on every file and directory beneath the target. It also makes uploaded or injected content easier to alter.
  • Writable deployed code. Files the web process executes or serves should not be writable by that process.
  • Blanket recursive changes. A -R run applies one mode to regular files, directories and scripts alike, so files that need execute permission and files that need only read permission get the same bits.

When a directory needs a different mode from its files, use find to target each type separately:

sudo find /var/www/example/public -type d -exec chmod 750 {} +

sudo find /var/www/example/public -type f -exec chmod 640 {} +

Run the directory and file commands separately, and only after confirming the tree contains no executable scripts that need a different mode.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Separate writable runtime data from served content

Some applications must write. Uploads, caches, session files, and temporary request bodies all need a writable location. The principle is to make only that location writable by the application identity, and keep executable code and static content outside it.

F5 NGINXaaS documentation states: “/var/www is a secure location for static content because the NGINX worker process can serve files from it but cannot modify them, ensuring content integrity.” This describes that platform’s policy. It is an example of the pattern, not a universal Linux default path.

On a self-managed server, a common layout is:

  • Static and deployed files: owned by a deployment account, readable by the server group, not writable by the server identity.
  • Runtime write directory (for example, an upload or cache path): owned by or writable to the application identity, placed outside the document root where possible.
  • If uploads must be inside the document root, configure the stack so uploaded files cannot be executed as code. Serve them with a handler that does not run scripts, and block script extensions in that path.

Where a writable directory is required, set it explicitly rather than widening everything else:

sudo chown deploy:www-data /var/www/example/uploads

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

sudo chmod 2770 /var/www/example/uploads

The leading 2 sets the setgid bit, so new files inherit the directory’s group. Test the result with the service identity (covered below) before relying on it.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Check how new files are created

Permissions for new files come from two sources: the creating process’s umask and, where present, default ACLs on the parent directory.

The umask removes bits from the mode a program requests. Linux man-pages documents the example 0666 & ~022 = 0644 (man-pages 6.19, 2026), which means a file requested with mode 0666 under umask 022 is created as 0644. That is an illustration of the arithmetic, not a statement that every server runs with umask 022.

To check the umask in a running service context:

  • Log in as the service account, or run sudo -u www-data sh -c 'umask', substituting your identity.
  • Inspect the service unit. For systemd-managed services, check for UMask= in the unit file or drop-in with systemctl cat nginx.
  • Check default ACLs on the parent: getfacl /var/www/example/uploads. Lines beginning with default: apply to new children.

If newly created files have surprising modes, the cause is usually one of these two. Fix it at the source instead of repairing each file afterwards.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Verify as the service identity

Testing as your administrator account can be misleading, because that account may have access the server does not. Run the checks as the server identity:

  1. Confirm read access to a served file: sudo -u www-data cat /var/www/example/public/index.html > /dev/null && echo readable
  2. Confirm traversal through each parent: sudo -u www-data test -x /var/www/example/public && echo traversable
  3. Confirm that deployed code cannot be written: sudo -u www-data test -w /var/www/example/public/index.html || echo not-writable
  4. Confirm the intended write location is writable: sudo -u www-data test -w /var/www/example/uploads && echo writable
  5. Request the page: curl -I http://localhost/ and check for 200 rather than 403.

Replace www-data and the paths with your actual identity and locations. If a command fails, the first check is the one that failed.

Troubleshoot when the modes look correct

If mode bits, ownership and traversal all look right but access still fails, check these in order:

  • Parent directories. Run namei -l on the full path again. A single directory without search permission for the server identity is the most common cause of a request that fails on an otherwise correct file.
  • Error logs. NGINX writes permission errors to its error log. Lines containing Permission denied point to filesystem or access-control problems, while a 403 response in the access log can also come from directory indexing or configuration rules rather than file permissions.
  • ACLs. An ACL that denies or masks access overrides the basic mode check for named users. Inspect with getfacl.
  • Mandatory access control. SELinux and AppArmor policy can deny access even when mode bits allow it. On SELinux systems, ls -Z shows file contexts. On AppArmor systems, check the kernel audit or journal messages for denials tied to the server process.
  • Mount options and containers. Filesystems mounted with restrictive options, network filesystems with mapped identities, and containers that map user IDs can make the effective identity differ from the one you see on the host.

Use each finding to narrow the cause before changing permissions again. Repeated broadening of modes usually hides the real problem and adds exposure.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep changes reviewable

Permission problems often get fixed by trial and error. A simple habit prevents that: record the output of namei -l and stat before a change, make the smallest change that matches the diagnosis, and run the verification commands again afterwards. Where a deployment tool manages the tree, make permission changes in that tool so they survive the next deploy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.