0

Reverse proxy — Nginx/Caddy sitting in front of PHP-FPM

Beginner5 min read·eng-18-012
interview

Concept

Reverse proxy — a server that sits in front of backend servers, accepts client requests, forwards them to the appropriate backend, and returns the response to the client. The client never directly communicates with the backend server.

Forward proxy vs reverse proxy:

  • Forward proxy: Sits in front of CLIENTS. Client → Forward Proxy → Internet. Used for anonymity or filtering. The server doesn't know the real client.
  • Reverse proxy: Sits in front of SERVERS. Client → Reverse Proxy → Server. The client doesn't know which backend server it's talking to.

What a reverse proxy does:

  • SSL termination: Handles HTTPS so backend servers don't need to.
  • Load balancing: Distributes requests across multiple backends.
  • Caching: Caches responses (Nginx serves cached HTML without hitting PHP).
  • Compression: Compresses responses (gzip/brotli) before sending to client.
  • Static file serving: Serves static files (JS, CSS, images) directly, never hitting PHP.
  • Security: Hides backend server details, IP addresses, ports. Rate limiting. WAF.
  • Request buffering: Buffers slow clients so PHP-FPM finishes fast and hands off.

Nginx as reverse proxy for PHP-FPM: The most common PHP setup. Nginx serves static files directly, forwards .php requests to PHP-FPM via FastCGI.

Other reverse proxies: Caddy (auto-HTTPS), Traefik (Kubernetes-native), HAProxy, Apache, AWS ALB, Cloudflare.

FastCGI: The protocol Nginx uses to communicate with PHP-FPM. Not HTTP — a binary protocol optimized for long-lived backend processes.

Code Example

nginx
# Nginx as reverse proxy for Laravel + PHP-FPM

server {
    listen 80;
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL certificates
    ssl_certificate     /etc/ssl/certs/example.com.pem;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    root /var/www/current/public;
    index index.php;

    # 1. Serve static files DIRECTLY (never touches PHP)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    # 2. All other requests — try static file, then pass to PHP
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 3. PHP files — forward to PHP-FPM via FastCGI
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.4-fpm.sock; # or app:9000 for Docker
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

        # Buffer entire request before forwarding to PHP-FPM
        # Prevents slow clients from holding PHP-FPM processes
        fastcgi_request_buffering on;
        fastcgi_read_timeout 120;
    }

    # 4. Security headers (set once at reverse proxy)
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy strict-origin-when-cross-origin;
    add_header Content-Security-Policy "default-src 'self'";

    # 5. Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
    gzip_min_length 1000;
}
php
<?php
// Laravel needs to know it's behind a reverse proxy
// TrustProxies middleware in bootstrap/app.php or Kernel.php

use Illuminate\Http\Middleware\TrustProxies;
use Illuminate\Http\Request;

class TrustProxies extends Middleware
{
    protected $proxies = '10.0.0.0/8'; // your reverse proxy's IP range
    // or '*' to trust all proxies (simpler but less secure)

    protected $headers = Request::HEADER_X_FORWARDED_FOR
        | Request::HEADER_X_FORWARDED_HOST
        | Request::HEADER_X_FORWARDED_PROTO;
}

// Now $request->ip() returns client's real IP (from X-Forwarded-For)
// And $request->secure() returns true if original request was HTTPS