Reverse proxy for GLPI: Nginx and Caddy configuration

Reverse proxy for GLPI: Nginx and Caddy configuration

GLPI ships with Apache as its default web server in most distros. That works for a single internal instance, but most production deployments end up putting GLPI behind a reverse proxy — for TLS termination, multiple hostnames, security headers, and consolidation with other services on the same host. This article is the working configuration for the two most common choices: Nginx and Caddy.

Why a reverse proxy at all

The questions "do I really need this" and "what does it actually solve" are worth answering up-front:

  • TLS termination — terminating HTTPS at the proxy means GLPI's PHP/Apache layer can speak plain HTTP locally. Cert renewal happens in one place.
  • Hostname routing — one server can host glpi.company.sk, monitoring.company.sk, wiki.company.sk. The proxy routes by Host header.
  • Security headers — HSTS, CSP, X-Frame-Options, Referrer-Policy can be added at the proxy without changing GLPI's PHP source.
  • Caching of static assets — JS, CSS, images cached at the proxy reduces load on the PHP backend.
  • Bot/abuse mitigation — rate limiting, IP allowlists for admin paths, basic WAF rules belong at the proxy layer.

Nginx configuration

Nginx is the heavyweight option — robust, widely deployed, well-understood. Working config for /etc/nginx/sites-available/glpi.conf:

server {
    listen 80;
    server_name glpi.company.sk;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name glpi.company.sk;

    ssl_certificate     /etc/letsencrypt/live/glpi.company.sk/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/glpi.company.sk/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    client_max_body_size 100M;  # GLPI attachment uploads

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_read_timeout 300s;
    }

    # Static assets cached aggressively
    location ~* \.(js|css|png|jpg|svg|woff2)$ {
        proxy_pass http://127.0.0.1:8080;
        proxy_cache_valid 200 7d;
        add_header Cache-Control "public, max-age=604800";
    }
}

The local Apache (or PHP-FPM) listens on 127.0.0.1:8080; only Nginx is exposed externally on 80/443.

Caddy configuration

Caddy is the simpler option — automatic Let's Encrypt, sensible defaults, single short config file. Same scenario in /etc/caddy/Caddyfile:

glpi.company.sk {
    reverse_proxy 127.0.0.1:8080 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto https
    }

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    request_body {
        max_size 100MB
    }

    @static {
        path *.js *.css *.png *.jpg *.svg *.woff2
    }
    header @static Cache-Control "public, max-age=604800"
}

Caddy auto-provisions and renews the TLS certificate on first request — no separate certbot process, no manual renewal cron. For most GLPI deployments this is enough; reach for Nginx when you need its more granular tuning options or you already run it for other services.

What to change in GLPI itself

Behind a reverse proxy, GLPI sees the request as coming from 127.0.0.1, not the original client IP. To make audit logs and rate limiting work properly:

  1. In Setup → General → System → Trusted Proxies, add 127.0.0.1 (or the proxy's IP).
  2. GLPI will then trust the X-Forwarded-For header for client IP attribution.
  3. If GLPI's URL config uses http:// internally, update Setup → General → URL of the application to the public https://glpi.company.sk. This affects email notifications, password-reset links, and OG images.

Forgetting step 3 is a common gotcha — emails come out with http:// links to localhost, which look like phishing.

Common pitfalls

  • Mixed content warnings — if GLPI's URL is set to http:// but the proxy serves https://, browsers warn on every page. Fix the URL setting.
  • Large file uploads fail — both proxy and PHP need client_max_body_size / upload_max_filesize raised. 100M is sensible for GLPI attachments.
  • Long-running cron via API hits proxy timeout — set proxy_read_timeout 300s for the API path or run cron via CLI directly on the box.
  • Security headers conflict with iframe embeds — if you embed GLPI in another internal portal, X-Frame-Options: SAMEORIGIN may need to become ALLOW-FROM or be replaced with CSP frame-ancestors.

Choosing between them

Pick Caddy if you're starting fresh, want minimal config, and don't already have an Nginx skill base. Pick Nginx if you already run it for other services, need granular tuning, or want to share patterns across deployments. Both serve GLPI well; the choice is mostly about what fits your team's existing operations. For a fuller picture of GLPI deployment topology see the GLPI SaaS / hosting overview.

Need help with this topic?

Get in touch