chmod for Web Server Directories: Secure Permission Patterns

May 28, 20267 min read

Why Web Server Permissions Matter

Misconfigured web server permissions are a leading cause of security breaches. If a web root is world-writable, an attacker who finds any code execution vulnerability can modify your site, plant malware, or steal data. If permissions are too restrictive, the server cannot serve pages at all.

The goal: set permissions that are restrictive enough to prevent unauthorized writes, but permissive enough for the server to function normally.

The Standard Web Server Permission Model

Web servers like Apache and Nginx run as a specific user (often www-data on Debian/Ubuntu or nginx on RHEL/CentOS). Your files need to be readable by that user — but not writable unless there is a specific reason.

Directory Structure

/var/www/
├── html/           # Document root (public files)
│   ├── index.html
│   ├── css/
│   └── js/
├── uploads/        # User-uploaded files
├── logs/           # Server logs
└── config/         # Configuration files with secrets
PathPermissionsModeOwnerRationale
/var/www/htmlrwxr-xr-x755root:www-dataOwner manages, server reads
/var/www/html/*rw-r--r--644root:www-dataStatic files, server reads only
/var/www/uploadsrwxrwx---770root:www-dataServer can write, others cannot
/var/www/configrwx------700root:rootOnly root can access
/var/www/config/*.phprw-------600root:rootSecrets — no group or others
/var/www/logsrwxr-x---750root:www-dataServer writes, group reads

Applying Permissions

Set Up a New Web Root

# Create directory structure
sudo mkdir -p /var/www/html /var/www/uploads /var/www/logs /var/www/config

# Set ownership
sudo chown -R root:www-data /var/www

# Set directory permissions
sudo find /var/www -type d -exec chmod 755 {} +

# Set file permissions
sudo find /var/www -type f -exec chmod 644 {} +

# Tighten config directory
sudo chmod 700 /var/www/config
sudo find /var/www/config -type f -exec chmod 600 {} +

# Open uploads directory for server writes
sudo chmod 770 /var/www/uploads

Using ACLs for Granular Control

When multiple developers need different access levels, POSIX ACLs provide finer control than traditional owner/group/others:

# Give developer "alice" read/write access to the web root
sudo setfacl -R -m u:alice:rwx /var/www/html

# Ensure new files inherit the ACL
sudo setfacl -R -d -m u:alice:rwx /var/www/html

Upload Directories: The Danger Zone

Upload directories are the highest-risk part of any web server because they allow external input to become files on your system.

Critical Rules

  1. Never allow execution in upload directories. If using Apache, add an .htaccess:
# Disable script execution in uploads
<FilesMatch "\.ph(p[2-7]?|t|tml)$">
    Require all denied
</FilesMatch>
  1. Separate uploads from the document root. Serve uploads through a script or a separate virtual host, not directly from /var/www/html/uploads.
  2. Limit file sizes at the server level (Nginx: client_max_body_size, Apache: LimitRequestBody).
  3. Validate file types server-side — never trust file extensions from the client.

Nginx: Deny PHP Execution in Uploads

location /uploads/ {
    location ~ \.php$ {
        deny all;
    }
}

Common Mistakes

Mistake 1: chmod -R 777 /var/www

This makes every file writable by every user on the system. If any service is compromised, the attacker can modify your website at will.

Fix: Use 755 for directories and 644 for files, always.

Mistake 2: Running the Server as root

If the webserver process runs as root, every vulnerability gives the attacker root access.

Fix: The server should run as an unprivileged user (www-data, nginx, etc.).

Mistake 3: Config Files World-Readable

Database passwords, API keys, and secrets in PHP/Python config files must not be readable by "others."

Fix: Mode 600, owned by root. The server reads them via group or ACL if needed.

Mistake 4: Uploads in the Document Root Without Execution Block

Even if you validate file types, an attacker might upload a .php file disguised as an image.

Fix: Block script execution in upload directories at the server level.

Permission Verification Script

#!/bin/bash
echo "=== Web Server Permission Audit ==="
echo "Document root:"
find /var/www/html -type d ! -perm 755 -ls 2>/dev/null
find /var/www/html -type f ! -perm 644 -ls 2>/dev/null

echo "Upload directory:"
stat -c "%a %n" /var/www/uploads

echo "Config directory:"
stat -c "%a %n" /var/www/config
find /var/www/config -type f ! -perm 600 -ls 2>/dev/null

echo "World-writable files:"
find /var/www -xdev -type f -perm -o+w -ls 2>/dev/null

Run this periodically or in CI to catch permission drift.

Key Takeaways

  • Directories: 755, static files: 644, config files: 600 — these are your defaults
  • Never use 777 in production; it is always wrong
  • Separate upload directories from the document root and block script execution
  • Run the web server as an unprivileged user, never as root
  • Use ACLs when traditional permissions are not flexible enough
  • Audit permissions regularly with a verification script

Try It Yourself

Visualize permission settings for any web server path using our chmod Calculator. Toggle bits for owner, group, and others, then copy the exact chmod command for your setup.