Apache 403: "Client Denied by Server Configuration"

Apache's configuration denied the request before it reached your app: Directory permissions/Require rules, filesystem permissions, or missing DirectoryIndex. The error log names the satisfied/failed condition — it is the whole diagnosis.

What you'll see

Root causes

Require rules / Directory access control

Apache 2.4's Require directives: Require all denied in the wrong scope, a leftover Order/Deny (2.2 syntax) that silently does nothing in 2.4, or Require ip blocks excluding your client. The AH01630 log line identifies the failing rule.

Filesystem permissions (the OS-level denial)

The apache user must have read (and +x on directories) access to every path component. After migrations or as different users create files, a single 700 directory anywhere in the path yields 403 even with correct Apache config.

Missing index or Options -Indexes on a directory URL

DirectoryIndex not finding index.html/index.php + Options -Indexes = 403 on directory paths while direct file paths serve fine. The distinguishing signature: files work, directories don't.

Fix it

  1. Read the exact denial from the log
    tail -5 /var/log/apache2/error.log   # AH01630 names the path and module
  2. Grant directory access explicitly for 2.4
    <Directory /var/www/app>\n  Require all granted\n</Directory>   # then: apachectl configtest && systemctl reload apache2
  3. Verify filesystem permissions along the whole path
    namei -l /var/www/app/index.html   # shows every path component's permissions at once
  4. Directory URLs: ensure an index and allow it
    # DirectoryIndex index.php index.html  +  Options +Indexes only where listing is intended

Field note

2.2→2.4 migrations break here constantly: Order allow,deny / Allow from all silently fail on 2.4 (wrong module), converting a working vhost into blanket 403s. The migration path is Require all granted. namei -l is the one-command permission audit: it walks every directory in the path so a single root-owned 700 hop can't hide.

Common questions

Why do I get 403 only from some IPs or paths?

Scoped Require rules: a Require ip allowlist in a Location/Directory block denies everyone else. The error log's path + the vhost's rule order tell you which block matched — restrictive blocks override later permissive ones.

Permissions look fine in the web root. Why still 403?

A parent directory denies traversal: every component from / down needs +x for the apache user. namei -l /path/to/file reveals the hop that's missing it — usually a home dir or a 750 created by a deployment user.

Ship it right the first time

Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.

Browse the template store →

One-time. Yours to modify. Instant download from the NinjaOps template store.