Full Caddyfile for home server

1. Caddy version (caddy version):

pi@naspi:~/rpi4_server $ docker images | grep caddy
caddy                      latest              8b01501010b8        6 days ago          31.7MB

2. How I run Caddy:

Using docker-compose file, an .env file and a Caddyfile.

a. System environment:

Running on a Raspberrypi 4, 4gb with classic Raspberrypi OS (debian)

pi@naspi:~/rpi4_server $ docker --version
Docker version 19.03.13, build 4484c46
pi@naspi:~/rpi4_server $ docker-compose --version
docker-compose version 1.27.4, build unknown

Installed docker-compose with pip3 as armv7 does not have a suitable package from github.

b. Command:

# full homeserver
docker-compose up -d 
# just caddy
docker-compose up caddy

c. Service/unit/compose file:

version: "3.8"
services:
  sonarr:
    image: linuxserver/sonarr
    container_name: sonarr
    restart: unless-stopped
    ports:
      - "${SONARR_PORT}:8989"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
    volumes:
      - ${APPDATA_ROOT}/sonarr:/config
      - ${MEDIA_ROOT}:/media
  radarr:
    image: linuxserver/radarr
    container_name: radarr
    restart: unless-stopped
    ports:
      - "${RADARR_PORT}:7878"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
    volumes:
      - ${APPDATA_ROOT}/radarr:/config
      - ${MEDIA_ROOT}:/media
  jackett:
    image: linuxserver/jackett
    container_name: jackett
    restart: unless-stopped
    ports:
      - "${JACKETT_PORT}:9117"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
    volumes:
      - ${APPDATA_ROOT}/jackett:/config
      - ${APPDATA_ROOT}/jackett/downloads:/downloads
  heimdall:
    image: linuxserver/heimdall
    container_name: heimdall
    restart: always
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
    ports:
      - "${HEIMDALL_PORT}:80"
      - "${HEIMDALL_SSL_PORT}:443"
    volumes:
      - ${APPDATA_ROOT}/heimdall:/config
  qbittorrent:
    image: linuxserver/qbittorrent
    container_name: qbittorrent
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
      - WEBUI_PORT=${QBITTORRENT_PORT}
    volumes:
      - ${APPDATA_ROOT}/qbittorrent:/config
      - ${MEDIA_ROOT}/downloads:/downloads/media
      - ${EXECUTABLES_ROOT}/downloads:/downloads/executables
    ports:
      - 6881:6881
      - 6881:6881/udp
      - "${QBITTORRENT_PORT}:${QBITTORRENT_PORT}"
    restart: unless-stopped
  plex:
    image: linuxserver/plex
    container_name: plex
    network_mode: host
    ports: 
      - "${PLEX_PORT}:32400"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - VERSION=docker
      - PLEX_CLAIM=claim-REDACTED #optional
    volumes:
      - ${APPDATA_ROOT}/plex:/config
      - ${MEDIA_ROOT}:/media
    restart: unless-stopped
    devices:
      - /dev/dri:/dev/dri #optional
      - /dev/vcsm:/dev/vcsm #optional
      - /dev/vchiq:/dev/vchiq #optional
      - /dev/video10:/dev/video10 #optional
      - /dev/video11:/dev/video11 #optional
      - /dev/video12:/dev/video12 #optional
  samba:
    image: dperson/samba
    container_name: samba
    ports:
      - "137:137/udp"
      - "138:138/udp"
      - "139:139/tcp"
      - "445:445/tcp"
    environment: 
      - TZ=${TIMEZONE}
    volumes: 
      - ${APPDATA_ROOT}:/appdata
      - ${MEDIA_ROOT}:/media
      - ${CLOUD_ROOT}:/cloud
      - ${EXECUTABLES_ROOT}:/executables
    restart: unless-stopped
    # need to add all of the volumes here in the command.
    # the yes;no; stuff is for setting permissions
    command: '-s "Appdata";/appdata;yes;no;yes;;;;
              -s "Media";/media;yes;no;yes;;;;
              -s "Cloud";/cloud;yes;no;yes;;;;
              -s "Executables";/executables;yes;no;yes;;;;'
  portainer:
    image: portainer/portainer
    container_name: portainer
    restart: always
    ports:
      - "${PORTAINER_PORT}:9000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${APPDATA_ROOT}/portainer:/data
  caddy:
    container_name: caddy
    image: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    env_file: 
      - .env
    volumes:
      - $PWD/Caddyfile:/etc/caddy/Caddyfile
      # - $PWD/site:/srv
      - ${APPDATA_ROOT}/caddy/data:/data
      - ${APPDATA_ROOT}/caddy/config:/config

d. My complete Caddyfile or JSON config:

naspi.local {

    # default -> pihole
    # reverse_proxy 192.168.0.69:443
    redir /heimdall /heimdall/
    reverse_proxy /heimdall/* 192.168.0.69:{$HEIMDALL_PORT}
    redir /portainer /portainer/
    reverse_proxy /portainer/* 192.168.0.69:{$PORTAINER_PORT}

    # media
    redir /sonarr /sonarr/
    reverse_proxy /sonarr/* 192.168.0.69:{$SONARR_PORT}
    redir /radarr /radarr/
    reverse_proxy /radarr/* 192.168.0.69:{$RADARR_PORT}
    redir /jackett /jackett/
    reverse_proxy /jackett/* 192.168.0.69:{$JACKETT_PORT}
    redir /plex /plex/
    reverse_proxy /plex/* 192.168.0.69:{$PLEX_PORT} #{
    #    encode gzip
    #   header {
    #        Strict-Transport-Security max-age=31536000;
    #        X-Content-Type-Options nosniff
    #        X-Frame-Options DENY
    #         Referrer-Policy no-referrer-when-downgrade
    #       X-XSS-Protection 1
    #    }
    #}

    # cloud 
    redir /nextcloud /nextcloud/
    reverse_proxy /nextcloud/* 192.168.0.69:{$NEXTCLOUD_PORT}

    # torrent
    #redir /qbittorrent /qbittorrent/
    #reverse_proxy /qbittorrent/* 192.168.0.69:{$QBITTORRENT_PORT} {
    #    without /qbittorrent
    #    transparent
    #    websocket
    #    header_upstream X-Forwarded-Host 192.168.0.69:{$QBITTORRENT_PORT}
    #    header_upstream -Origin
    #    header_upstream -Referer
    }
}

3. The problem I’m having:

I’m trying to get a working reverse proxy setup on each of my services that I run on my Rpi 4. I have sucessfully reversed sonarr, radarr and jackett as there was a lot to find on the internet. Qbittorrent / plex are not as easy and after reading the reverse_proxy directives I don’t understand what I need to add to get it working.

First I would like to reverse proxy everything in my local domain, by accessing in my example naspi.local/serviceXX or naspi.local/serviceYY. I would prefer if it was serviceXX.naspi.local but as far as I understand this is not possible in the local network. Any tips regarding this are helpful.

Secondly, after my local reverse proxy is operational I want to also point to a domain I own.

4. Error messages and/or full log output:

Just getting white pages / nothing is rendered.

Plex error : 401 unauthorized
Qbittorrent: no page rendered

5. What I already tried:

I’ve already tried several things, as can be seen commented in the Caddyfile, but I just don’t know where to begin with writing these directives.

Most of the things I can find on reverse proxying for example qbittorrent are from the V1 version and I’m not entirely sure how to migrate them to v2. I’ve tried several options, such as adding the header_upstream for X-forwarded, or the transparent option. Also, I’ve tried disabling CSRF in qbittorrent, but to no avail.

I can also not find a generic home server Caddy v2 configuration and this can contribute greatly to the community as a guideline for others.

6. Links to relevant resources:

It is possible, you just need to update your hosts file on your machine, or run a local DNS server (like CoreDNS) which will resolve those names to your Pi’s LAN IP.

For some of these services, you may be running into this, because of using a subpath rather than a subdomain:

You may also find some useful information here:

Could you be more specific about what’s not working? What are the symptoms? What’s in your logs? What happens if you use curl -v to make a request (to see the HTTP headers and such)?

Thanks for your reply and help.

What do you mean with

you just need to update your hosts file on your machine

Do I add for example this to my /etc/hosts file on the raspberry pi (with ip 192.168.0.69) itself?

192.168.0.69 naspi

My logs are jammed with errors:

pi@naspi:~/rpi4_server $ docker-compose restart caddy
Restarting caddy ... done
pi@naspi:~/rpi4_server $ docker logs caddy
{"level":"info","ts":1603227945.7491028,"msg":"shutting down apps then terminating","signal":"SIGTERM"}
{"level":"info","ts":1603227946.7505386,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0x31143c0"}
{"level":"error","ts":1603227946.7506223,"logger":"tls","msg":"job failed","error":"redir: obtaining certificate: context canceled"}
{"level":"info","ts":1603227946.750622,"logger":"tls.obtain","msg":"releasing lock","identifier":"redir"}
{"level":"error","ts":1603227946.7542496,"logger":"tls","msg":"job failed","error":"redir: obtaining certificate: context canceled"}
{"level":"info","ts":1603227947.2544422,"logger":"admin","msg":"stopped previous server"}
{"level":"info","ts":1603227947.254514,"msg":"shutdown done","signal":"SIGTERM"}
{"level":"info","ts":1603227949.794706,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
{"level":"info","ts":1603227949.8041763,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["[::1]:2019","127.0.0.1:2019","localhost:2019"]}
{"level":"info","ts":1603227949.8052876,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x3fde9b0"}
{"level":"info","ts":1603227949.8080056,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1603227949.8133824,"logger":"tls","msg":"setting internal issuer for automation policy that has only internal subjects but no issuer configured","subjects":["naspi.local"]}
{"level":"info","ts":1603227949.8239095,"logger":"tls.obtain","msg":"acquiring lock","identifier":"redir"}
{"level":"info","ts":1603227949.8253837,"logger":"tls.obtain","msg":"lock acquired","identifier":"redir"}
{"level":"info","ts":1603227949.8394737,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["redir"]}
{"level":"info","ts":1603227949.8397307,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["redir"]}
{"level":"info","ts":1603227949.8464046,"logger":"tls","msg":"cleaned up storage units"}
{"level":"info","ts":1603227950.0225399,"logger":"pki.ca.local","msg":"root certificate is already trusted by system","path":"storage:pki/authorities/local/root.crt"}
{"level":"info","ts":1603227950.0232942,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["naspi.local","redir"]}
{"level":"warn","ts":1603227950.0261343,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [naspi.local]: no OCSP server specified in certificate"}
{"level":"info","ts":1603227950.0277698,"msg":"autosaved config","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1603227950.0278249,"msg":"serving initial configuration"}
{"level":"info","ts":1603227950.030255,"logger":"tls.obtain","msg":"acquiring lock","identifier":"redir"}
{"level":"error","ts":1603227950.9079115,"logger":"tls.obtain","msg":"will retry","error":"[redir] Obtain: [redir] creating new order: request to https://acme-v02.api.letsencrypt.org/acme/new-order failed after 1 attempts: HTTP 400 urn:ietf:params:acme:error:rejectedIdentifier - Error creating new order :: Cannot issue for \"redir\": Domain name needs at least one dot (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":1.082452961,"max_duration":2592000}

Accessing the webpages of qbittorrent gives just a white page. Plex returns a “401 Unauthorized” error. It is unclear to me how I debug this kind of thing.
When I curl -v 192.168.0.69/qbittorrent I get the following:

* Expire in 0 ms for 6 (transfer 0x1338880)
*   Trying 192.168.0.69...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x1338880)
* Connected to 192.168.0.69 (192.168.0.69) port 80 (#0)
> GET /qbittorrent HTTP/1.1
> Host: 192.168.0.69
> User-Agent: curl/7.64.0
> Accept: */*
> 
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://192.168.0.69/qbittorrent
< Server: Caddy
< Date: Tue, 20 Oct 2020 21:07:38 GMT
< Content-Length: 0
< 
* Closing connection 0

Plex has the same output.

No, you’d need to do so on the machines connecting to your raspberry pi. Which is likely not possible to do on phones for example, so if you need access from all the devices at home, the DNS server option is better. You’ll just update your router’s settings to point to your local DNS server instead, which would then pass through anything not specifically configured to something like 8.8.8.8 which is Google’s DNS server.

As for the error message:

Cannot issue for “redir”

I think this is because of a syntax error in your Caddyfile somewhere. I’m not spotting it in your top post at a glance, but Caddy is reading one of your redir as a domain name, and Caddy is trying to get a certificate for that. Did you post your full, unmodified Caddyfile? I do notice one issue, you have an uncommented } near the end, but that’s a different problem than the error would imply.

curl -v 192.168.0.69/qbittorrent

Oh, well you configured Caddy to listen for requests to naspi.local, not that IP address. It won’t match that request then.

Also, Caddy does automatically configure HTTP->HTTPS redirects if Automatic HTTPS is activated, so it’s triggering a redirect to HTTPS there because you make the request over HTTP.

Alright, the hosts file is clear now.

The syntax error in the Caddyfile was indeed a {, more specifically the opening bracket after the qbittorrent entry. I no longer get the /redir/ errors

If I curl -v naspi.local/qbittorrent from the server itself I get many expire messages, followed by a “could not resolve host” message. The server is online. If I perform the same operation from my desktop I get the following

*   Trying 192.168.0.69:80...
* Connected to naspi.local (192.168.0.69) port 80 (#0)
> GET /qbittorrent HTTP/1.1
> Host: naspi.local
> User-Agent: curl/7.71.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://naspi.local/qbittorrent
< Server: Caddy
< Date: Tue, 20 Oct 2020 21:35:52 GMT
< Content-Length: 0
< 
* Closing connection 0

Which looks very similar to the output of the above.

When curling to the https host:

(base) floris@dev:~$ curl -v https://naspi.local/sonarr/
*   Trying 192.168.0.69:443...
* Connected to naspi.local (192.168.0.69) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /home/floris/anaconda3/ssl/cacert.pem
  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

And to qbittorrent

(base) floris@dev:~$ curl -v https://naspi.local/qbittorrent
*   Trying 192.168.0.69:443...
* Connected to naspi.local (192.168.0.69) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /home/floris/anaconda3/ssl/cacert.pem
  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

This topic was automatically closed after 30 days. New replies are no longer allowed.