Documentation
Alternate Web Servers
How to map DesertCMS public static files and CGI endpoints to nginx, Apache, and Caddy.
WEBSERVERS.mdOpenBSD 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.