Documentation

Alternate Web Servers

How to map DesertCMS public static files and CGI endpoints to nginx, Apache, and Caddy.

Source: WEBSERVERS.md

OpenBSD httpd is the reference deployment. Other web servers can work if they preserve the same routing model:

  • Serve generated public files from public_root.
  • Route dynamic paths to bin/desertcms.cgi.
  • Keep private originals outside the public root.
  • Allow large enough request bodies for admin photo uploads.
  • Terminate HTTPS before admin, shop, comments, ratings, analytics, and forms endpoints.

Dynamic paths currently include:

/admin
/analytics
/comments
/ratings
/forms
/shop

The shop webhook endpoint is:

/shop/stripe/webhook

nginx With fcgiwrap

On Linux, nginx usually needs fcgiwrap or another CGI bridge. Example shape:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

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

    root /var/www/desertcms;
    index index.html;
    client_max_body_size 64m;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location /assets/ {
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }

    location ~ ^/(admin|analytics|comments|ratings|forms|shop)(/.*)?$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /opt/desertcms/bin/desertcms.cgi;
        fastcgi_param SCRIPT_NAME /desertcms.cgi;
        fastcgi_param PATH_INFO $uri;
        fastcgi_param REQUEST_URI $request_uri;
        fastcgi_param HTTPS on;
        fastcgi_param DESERTCMS_CONFIG /etc/desertcms.conf;
        fastcgi_pass unix:/run/fcgiwrap.socket;
    }
}

Keep /assets/ static. Public media derivatives live there; private originals do not.

Apache

Apache can run CGI directly with mod_cgi or through fcgiwrap with mod_proxy_fcgi.

Simple CGI shape:

<VirtualHost *:80>
    ServerName example.com
    Redirect permanent / https://example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/desertcms

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    LimitRequestBody 67108864

    ScriptAliasMatch ^/(admin|analytics|comments|ratings|forms|shop)(/.*)?$ /opt/desertcms/bin/desertcms.cgi

    <Directory /var/www/desertcms>
        Require all granted
        Options -Indexes
        AllowOverride None
    </Directory>

    <Directory /opt/desertcms/bin>
        Require all granted
        Options +ExecCGI
        SetHandler cgi-script
        SetEnv DESERTCMS_CONFIG /etc/desertcms.conf
    </Directory>
</VirtualHost>

If Apache runs CGI as the web user, make sure that user is the intended DesertCMS service user or use a safer wrapper/suexec setup. The process needs write access to the database, theme directory, backup directory, public root, and private originals directory.

Caddy

Caddy v2 core serves static files and reverse proxies HTTP, but it does not execute CGI by itself. Use one of these patterns:

  • Caddy terminates TLS and serves static files, then reverse proxies dynamic paths to nginx or Apache on localhost.
  • Caddy terminates TLS and reverse proxies dynamic paths to a local CGI/FastCGI adapter that exposes HTTP.
  • Use a maintained CGI/FastCGI Caddy plugin only if you are willing to own that operational dependency.

Static plus reverse-proxy shape:

example.com {
    root * /var/www/desertcms
    encode zstd gzip

    @dynamic path /admin* /analytics* /comments* /ratings* /forms* /shop*
    reverse_proxy @dynamic 127.0.0.1:8081

    file_server
}

In this model, 127.0.0.1:8081 must be nginx, Apache, or a small local adapter that executes bin/desertcms.cgi with DESERTCMS_CONFIG=/etc/desertcms.conf.

Configure Caddy or the backend adapter to allow 64 MB admin uploads.

Required Behavior Checks

After changing web servers, verify:

curl -I https://example.com/
curl -I https://example.com/docs/
curl -I https://example.com/admin/login
curl -I https://example.com/assets/site.css
curl -I https://example.com/shop

Then test in a browser:

  • Login works.
  • Publishing rebuilds the public site.
  • Photo upload succeeds with a realistic JPEG.
  • /docs/ lists Markdown files.
  • Comments and ratings load on a public post.
  • Stripe webhook endpoint returns an expected non-browser response.

If /docs/ shows an empty state, confirm that docs_source_dir points to a readable directory containing .md files and run a rebuild as the CMS service user.