1. The problem I’m having:
How can I fix my Caddy configuration to serve demos of my work? I’m trying to serve demos of programming projects with Caddy. I’m learning programming with freeCodeCamp and Codecademy, and I recently started hosting my own Git server using Forgejo. Until now, I used GitHub Pages to deploy demos of my projects, which has meant pushing them to two remotes. This is becoming a pain, so I’d like to use Caddy to serve demos of my projects.
I’d like to serve them at “demos.laniecarmelo.tech/”. I have a Git hook that deploys the projects to a place where Caddy can access them, and this is working well. Projects are located at /var/www/demos/. In my caddy.json file, I’ve set up two routes, one of which redirects “demos.laniecarmelo.tech/” to my portfolio on my WordPress site. Without this route, Caddy complains about no index file. The second rule is supposed to serve projects in /var/www/demos to “demos.laniecarmelo.tech/”. The problem is that everything is being redirected to the portfolio.
2. Error messages and/or full log output:
[lanie@stormux ~] $ curl -vL https://demos.laniecarmelo.tech/
* Host demos.laniecarmelo.tech:443 was resolved.
* IPv6: (none)
* IPv4: 69.58.156.77
* Trying 69.58.156.77:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=demos.laniecarmelo.tech
* start date: Mar 3 18:57:26 2025 GMT
* expire date: Jun 1 18:57:25 2025 GMT
* subjectAltName: host "demos.laniecarmelo.tech" matched cert's "demos.laniecarmelo.tech"
* issuer: C=US; O=Let's Encrypt; CN=E5
* SSL certificate verify ok.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
* Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to demos.laniecarmelo.tech (69.58.156.77) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://demos.laniecarmelo.tech/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: demos.laniecarmelo.tech]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.12.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: demos.laniecarmelo.tech
> User-Agent: curl/8.12.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
< HTTP/2 404
< alt-svc: h3=":443"; ma=2592000
< content-security-policy: default-src 'self' https: 'unsafe-inline' 'unsafe-eval'; img-src https: data:; frame-src 'self' https:; object-src 'none'
< referrer-policy: strict-origin-when-cross-origin
< server: Caddy
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< content-length: 0
< date: Tue, 04 Mar 2025 22:12:04 GMT
<
* Connection #0 to host demos.laniecarmelo.tech left intact
[lanie@stormux ~] $ curl -vL https://demos.laniecarmelo.tech/dasmotos-arts-and-crafts
* Host demos.laniecarmelo.tech:443 was resolved.
* IPv6: (none)
* IPv4: 69.58.156.77
* Trying 69.58.156.77:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=demos.laniecarmelo.tech
* start date: Mar 3 18:57:26 2025 GMT
* expire date: Jun 1 18:57:25 2025 GMT
* subjectAltName: host "demos.laniecarmelo.tech" matched cert's "demos.laniecarmelo.tech"
* issuer: C=US; O=Let's Encrypt; CN=E5
* SSL certificate verify ok.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
* Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to demos.laniecarmelo.tech (69.58.156.77) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://demos.laniecarmelo.tech/dasmotos-arts-and-crafts
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: demos.laniecarmelo.tech]
* [HTTP/2] [1] [:path: /dasmotos-arts-and-crafts]
* [HTTP/2] [1] [user-agent: curl/8.12.1]
* [HTTP/2] [1] [accept: */*]
> GET /dasmotos-arts-and-crafts HTTP/2
> Host: demos.laniecarmelo.tech
> User-Agent: curl/8.12.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
< HTTP/2 404
< alt-svc: h3=":443"; ma=2592000
< content-security-policy: default-src 'self' https: 'unsafe-inline' 'unsafe-eval'; img-src https: data:; frame-src 'self' https:; object-src 'none'
< referrer-policy: strict-origin-when-cross-origin
< server: Caddy
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< content-length: 0
< date: Tue, 04 Mar 2025 22:12:33 GMT
<
* Connection #0 to host demos.laniecarmelo.tech left intact
3. Caddy version:
v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
4. How I installed and ran Caddy:
a. System environment:
Stormux, based on Arch Linux ARM. Version: Linux stormux 6.12.17-1-rpi-16k #1 SMP PREEMPT Fri Feb 28 16:02:14 MST 2025 aarch64 GNU/Linux
I built Caddy using Xcaddy and created a Systemd service for it.
b. Command:
sudo systemctl start caddy
c. Service/unit/compose file:
[Unit]
Description=Caddy web server
Documentation=https://caddyserver.com/
After=network.target
[Service]
User=caddy
Group=caddy
WorkingDirectory=/etc/caddy
ExecStart=/usr/bin/caddy run --config /etc/caddy/caddy.json
Restart=on-failure
StartLimitInterval=600
[Install]
WantedBy=multi-user.target
d. My complete Caddy config:
{
"admin": {
"listen": "127.0.0.1:2019",
"origins": ["localhost", "127.0.0.1", "caddy.laniecarmelo.tech"]
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"apps": {
"http": {
"http_port": 80,
"https_port": 443,
"servers": {
"srv0": {
"listen": [":443"],
"routes": [
{
"match": [{"host": ["*.laniecarmelo.tech"]}],
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Content-Security-Policy": [
"default-src 'self' https: 'unsafe-inline' 'unsafe-eval'; img-src https: data:; frame-src 'self' https:; object-src 'none'"
],
"Referrer-Policy": ["strict-origin-when-cross-origin"],
"Strict-Transport-Security": ["max-age=31536000; includeSubDomains; preload"],
"X-Content-Type-Options": ["nosniff"],
"X-Xss-Protection": ["1; mode=block"]
}
}
},
{
"handler": "encode",
"encodings": {
"br": {},
"gzip": {}
},
"prefer": ["br"]
}
],
"terminal": false
},
{
"match": [
{"host": ["demos.laniecarmelo.tech"]},
{"path": ["/{project}/*"]}
],
"handle": [
{
"handler": "rewrite",
"uri": "/{http.vars.project}/{path}"
},
{
"handler": "file_server",
"root": "/var/www/demos/{http.vars.project}",
"hide": [".git"],
"index_names": ["index.html", "index.htm"]
}
],
"terminal": true
},
{
"match": [
{"host": ["demos.laniecarmelo.tech"]},
{"path": ["/"]}
],
"handle": [
{
"handler": "static_response",
"body": "Redirecting to portfolio...",
"status_code": 301,
"headers": {
"Location": ["https://laniecarmelo.tech/accessible-programming-portfolio/"]
}
}
],
"terminal": true
},
{
"match": [{"host": ["home.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3000"}]}],
"terminal": true
},
{
"match": [{"host": ["read.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8000"}]}],
"terminal": true
},
{
"match": [{"host": ["bookmarks.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3009"}]}],
"terminal": true
},
{
"match": [{"host": ["rss.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3010"}]}],
"terminal": true
},
{
"match": [{"host": ["rss-bridge.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3015"}]}],
"terminal": true
},
{
"match": [{"host": ["miniflux-ai.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8083"}]}],
"terminal": true
},
{
"match": [{"host": ["n8n.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:5678"}]}],
"terminal": true
},
{
"match": [{"host": ["files.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3007"}]}],
"terminal": true
},
{
"match": [{"host": ["todo.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3456"}]}],
"terminal": true
},
{
"match": [{"host": ["caddy.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:2019"}]}],
"terminal": true
},
{
"match": [{"host": ["adguard.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3001"}]}],
"terminal": true
},
{
"match": [{"host": ["recipes.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8081"}]}],
"terminal": true
},
{
"match": [{"host": ["dockge.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:5001"}]}],
"terminal": true
},
{
"match": [{"host": ["dozzle.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8080"}]}],
"terminal": true
},
{
"match": [{"host": ["uptime.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3006"}]}],
"terminal": true
},
{
"match": [{"host": ["beszel.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8090"}]}],
"terminal": true
},
{
"match": [{"host": ["code.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3012"}]}],
"terminal": true
},
{
"match": [{"host": ["wiki.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:5000"}]}],
"terminal": true
},
{
"match": [{"host": ["git.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3008"}]}],
"terminal": true
},
{
"match": [{"host": ["irc.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3011"}]}],
"terminal": true
},
{
"match": [{"host": ["search.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8082"}]}],
"terminal": true
},
{
"match": [{"host": ["ai.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3080"}]}],
"terminal": true
},
{
"match": [{"host": ["cockpit.laniecarmelo.tech"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:3004"}]}],
"terminal": true
},
{
"match": [{"host": ["*.laniecarmelo.tech"]}],
"handle": [
{
"handler": "static_response",
"body": "Not Found",
"status_code": 404
}
],
"terminal": true
}
],
"logs": {
"skip_hosts": ["*.laniecarmelo.tech"]
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": ["*.laniecarmelo.tech"],
"issuers": [
{
"module": "acme",
"email": "laniecarmelo@gmail.com",
"challenges": {
"dns": {
"provider": {
"name": "cloudflare",
"api_token": "redacted"
}
}
}
}
]
}
]
}
}
}
}