Drupal + nginx local (macOS)

Stack

  • Homebrew: nginx, php (8.2+), MySQL/MariaDB as needed
  • Docroot: project docroot/ or web/ (Composer Drupal layouts)
  • URL pattern: http://example.localhost on port 80

Install & start

brew install nginx php
brew services start php
# nginx on :80 needs root:
sudo brew services start nginx

Confirm PHP-FPM listens (pick one in the vhost):

# TCP (common default)
lsof -i :9000
# or socket
ls "$(brew --prefix)/var/run/php-fpm.sock"

nginx.conf

Ensure http includes server configs:

http {
    include servers/*;
}

Path: $(brew --prefix)/etc/nginx/nginx.conf
Server files: $(brew --prefix)/etc/nginx/servers/example.conf

Server block template

Replace paths and fastcgi_pass as needed.

server {
    listen 80;
    server_name example.localhost;
    root /absolute/path/to/docroot;
    index index.php;
    client_max_body_size 64M;

    location / {
        try_files $uri /index.php?$query_string;
    }

    # Required for Drupal image styles + stage_file_proxy.
    # Do NOT use try_files that only serves existing files and returns 404
    # without hitting PHP — missing files must reach Drupal.
    location ~ ^/sites/.*/files/ {
        try_files $uri /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # Prefer socket if present, else TCP:
        # fastcgi_pass unix:/opt/homebrew/var/run/php-fpm.sock;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    location ~ /\. {
        deny all;
    }
}

Apply:

nginx -t
sudo nginx -s reload

DNS / hosts

Modern macOS resolves *.localhost to 127.0.0.1. If not:

sudo sh -c 'echo "127.0.0.1 example.localhost" >> /etc/hosts'

Drupal settings

sites/default/settings.local.php (or include from settings.php):

$settings['trusted_host_patterns'] = [
  '^example\.localhost$',
];

// Optional: pull files from production (no trailing slash)
$config['stage_file_proxy.settings']['origin'] = 'https://www.example.com';

DB: point $databases at local MySQL, or use whatever local DB you run.

Checklist after config

  1. root points at Drupal web root (index.php visible there)
  2. Module stage_file_proxy enabled only on non-prod; origin = real prod HTTPS host (not a redirect-only domain)
  3. curl -I http://example.localhost → 200/302 from Drupal, not nginx 403/404
  4. Port conflict: lsof -i :80 — stop Apache/DDEV/other nginx first

Common failures

Symptom Cause
502 Bad Gateway PHP-FPM not running or wrong fastcgi_pass
404 on /sites/default/files/... nginx serves files dir without fallback to index.php
Blank / permission errors Wrong root path or PHP user can’t read files
Trusted host error Missing trusted_host_patterns for example.localhost
stage_file_proxy no images Wrong origin, or files location bypasses Drupal

Bijlagen

Geen bestanden gekoppeld.