Setting up PeerTube with Caddy

1. Output of caddy version:

v2.5.1 h1:bAWwslD1jNeCzDa+jDCNwb8M3UJ2tPa8UZFFzPVmGKs=

2. How I run Caddy:

a. System environment:

Custom Docker image:

FROM caddy:builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare

FROM caddy:latest

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

b. Command:

docker compose up -d

c. Service/unit/compose file:

services:
  caddy:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: caddy
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - data:/data
      - config:/config
    environment:
      - PUID=1000
      - PGID=1000
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
      - SMTP_PASSWORD=${SMTP_PASSWORD}
      - PERSONAL_EMAIL=${PERSONAL_EMAIL}
volumes:
  data:
  config:
  log:
networks:
  default:
    name: reverse_proxy
    external: true

reverse_proxy is an external Docker network that I put my other Docker services on, so Caddy can reverse proxy them.

PeerTube Docker compose file:

services:
  # You can comment this webserver section if you want to use another webserver/proxy or test PeerTube in local
  peertube:
    image: chocobozzz/peertube:production-bullseye
    container_name: peertube
    hostname: peertube
    env_file:
      - .env
    volumes:
      - assets:/app/client/dist
      - data:/data
      - config:/config
    depends_on:
      - postgres
      - redis
    networks:
      - default
      - proxy
    restart: "always"
  postgres:
    image: postgres:13-alpine
    container_name: peertube-db
    hostname: peertube-db
    env_file:
      - .env
    volumes:
      - db:/var/lib/postgresql/data
    networks:
      - default
    restart: "always"
  redis:
    image: redis:6-alpine
    container_name: peertube-redis
    volumes:
      - redis:/data
    networks:
      - default
    restart: "always"
volumes:
  data:
  config:
  assets:
  db:
  redis:
networks:
  default:
  proxy:
    name: reverse_proxy
    external: true

PeerTube .env file:

# Database / Postgres service configuration
POSTGRES_USER=peertube
POSTGRES_PASSWORD=[REDACTED]
# Postgres database name "peertube"
POSTGRES_DB=peertube
# Editable only with a suffix :
#POSTGRES_DB=peertube_prod
#PEERTUBE_DB_SUFFIX=_prod
PEERTUBE_DB_USERNAME=peertube
PEERTUBE_DB_PASSWORD=[REDACTED]
PEERTUBE_DB_SSL=false
# Default to Postgres service name "postgres" in docker-compose.yml
PEERTUBE_DB_HOSTNAME=peertube-db

# PeerTube server configuration
# If you test PeerTube in local: use "peertube.localhost" and add this domain to your host file resolving on 127.0.0.1
PEERTUBE_WEBSERVER_HOSTNAME=haddock.cc
# If you just want to test PeerTube on local
#PEERTUBE_WEBSERVER_PORT=9000
#PEERTUBE_WEBSERVER_HTTPS=false
# If you need more than one IP as trust_proxy
# pass them as a comma separated array:
PEERTUBE_TRUST_PROXY=["127.0.0.1", "loopback", "172.18.0.0/16"]

# E-mail configuration
# If you use a Custom SMTP server
PEERTUBE_SMTP_USERNAME=apikey
PEERTUBE_SMTP_PASSWORD=[REDACTED]
# Default to Postfix service name "postfix" in docker-compose.yml
# May be the hostname of your Custom SMTP server
PEERTUBE_SMTP_HOSTNAME=smtp.sendgrid.net
PEERTUBE_SMTP_PORT=587
PEERTUBE_SMTP_FROM=noreply@haddock.cc
PEERTUBE_SMTP_TLS=false
PEERTUBE_SMTP_DISABLE_STARTTLS=false
PEERTUBE_ADMIN_EMAIL=[REDACTED]

# /!\ Prefer to use the PeerTube admin interface to set the following configurations /!\
#PEERTUBE_SIGNUP_ENABLED=true
#PEERTUBE_TRANSCODING_ENABLED=true
#PEERTUBE_CONTACT_FORM_ENABLED=true

d. My complete Caddy config:

(auth) {
        forward_auth authelia:9091 {
                uri /api/verify?rd=https://auth.haddock.cc
                copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
        }
}

{ # Global configuration
    acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    email {env.PERSONAL_EMAIL}
}

auth.haddock.cc { # Auth portal
    reverse_proxy authelia:9091
}

cloud.haddock.cc {
    # import auth
    encode zstd
    reverse_proxy nextcloud:80 {
        header_down Strict-Transport-Security "max-age=15552000; includeSubDomains"
        header_down X-Content-Type-Options "nosniff"
        header_down X-XSS-Protection "1; mode=block"
        header_down X-Robots-Tag "none"
        header_down X-Frame-Options "SAMEORIGIN"
        header_down Referrer-Policy "no-referrer"
    }
    rewrite /.well-known/carddav /remote.php/dav
    rewrite /.well-known/caldav /remote.php/dav
}

media.haddock.cc {
    # authorize with mypolicy
    reverse_proxy jellyfin:8096
}

photos.haddock.cc {
    import auth
    reverse_proxy photoprism:2342
}

prowl.haddock.cc { # Prowlarr
    import auth
    reverse_proxy prowlarr:9696
}

tv.haddock.cc { # Sonarr
    import auth
    reverse_proxy sonarr:8989
}

movies.haddock.cc { # Radarr
    import auth
    reverse_proxy radarr:7878
}

books.haddock.cc { # Readarr
    import auth
    reverse_proxy readarr:8787
}

guard.haddock.cc { # AdGuard Home
    import auth
    reverse_proxy adguard:80
}

cctv.haddock.cc { # Frigate
    import auth
    reverse_proxy frigate:5000
}

tube.haddock.cc { # PeerTube
    reverse_proxy peertube:9000
}

localhost:8112 { # QBittorrent
    reverse_proxy torrent:8080
}

3. The problem I’m having:

When I go to https://tube.haddock.cc, I get a box in the bottom left corner that says

Cannot retrieve OAuth Client credentials: undefined. Ensure you have correctly configured PeerTube (config/ directory), in particular the “webserver” section.

I also can’t login. When I try, I get this error in the PeerTube log:

[haddock.cc:443] 2022-09-29 18:28:02.601 error: Client log: Backend returned code 400, errorMessage is: Invalid client: client is invalid {
  "userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0",
  "url": "https://tube.haddock.cc/login"
}

4. Error messages and/or full log output:

The Caddy log made this post go way over the character limit, but here’s the part from when I get the error in PeerTube:

{"level":"debug","ts":1664477193.3474128,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"peertube:9000","duration":0.005960314,"request":{"remote_ip":"172.71.150.44","remote_port":"30510","proto":"HTTP/2.0","method":"POST","host":"tube.haddock.cc","uri":"/api/v1/server/logs/client","headers":{"Referer":["https://tube.haddock.cc/"],"Origin":["https://tube.haddock.cc"],"Accept-Encoding":["gzip"],"Cf-Ray":["7526e1da4b58c381-SEA"],"Accept-Language":["en-US,en;q=0.5"],"Content-Type":["application/json"],"Sec-Fetch-Site":["same-origin"],"Accept":["application/json"],"X-Forwarded-Host":["tube.haddock.cc"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["https"],"Cf-Ipcountry":["US"],"X-Forwarded-For":["172.71.150.44"],"Content-Length":["316"],"Cookie":[],"Cf-Connecting-Ip":["63.231.52.232"],"Cf-Visitor":["{\"scheme\":\"https\"}"],"Cdn-Loop":["cloudflare"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0"],"Sec-Fetch-Mode":["cors"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"tube.haddock.cc"}},"headers":{"Access-Control-Expose-Headers":["Retry-After"],"X-Ratelimit-Limit":["10"],"Date":["Thu, 29 Sep 2022 18:46:33 GMT"],"X-Ratelimit-Reset":["1664477218"],"Etag":["W/\"2a-UpTsLJ74nYuiLgNgEwlQMxGqwrE\""],"Connection":["keep-alive"],"X-Frame-Options":["DENY"],"Tk":["N"],"Access-Control-Allow-Origin":["*"],"X-Ratelimit-Remaining":["0"],"Content-Length":["42"],"Retry-After":["600"],"Content-Type":["text/html; charset=utf-8"],"Access-Control-Allow-Credentials":["true"],"Keep-Alive":["timeout=5"]},"status":429}
{"level":"debug","ts":1664477193.3513138,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"peertube:9000","duration":0.007514071,"request":{"remote_ip":"172.71.150.44","remote_port":"30196","proto":"HTTP/2.0","method":"POST","host":"tube.haddock.cc","uri":"/api/v1/server/logs/client","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["tube.haddock.cc"],"Cookie":[],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0"],"Content-Length":["251"],"Origin":["https://tube.haddock.cc"],"Content-Type":["application/json"],"Cf-Visitor":["{\"scheme\":\"https\"}"],"Referer":["https://tube.haddock.cc/"],"X-Forwarded-For":["172.71.150.44"],"Sec-Fetch-Dest":["empty"],"Cf-Connecting-Ip":["63.231.52.232"],"X-Forwarded-Proto":["https"],"Accept-Encoding":["gzip"],"Accept":["application/json"],"Cdn-Loop":["cloudflare"],"Cf-Ipcountry":["US"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Site":["same-origin"],"Cf-Ray":["7526e1da4b55c381-SEA"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"tube.haddock.cc"}},"headers":{"Access-Control-Allow-Credentials":["true"],"X-Ratelimit-Limit":["10"],"Retry-After":["600"],"Etag":["W/\"2a-UpTsLJ74nYuiLgNgEwlQMxGqwrE\""],"Connection":["keep-alive"],"X-Frame-Options":["DENY"],"Access-Control-Expose-Headers":["Retry-After"],"Tk":["N"],"Access-Control-Allow-Origin":["*"],"X-Ratelimit-Remaining":["0"],"X-Ratelimit-Reset":["1664477218"],"Content-Type":["text/html; charset=utf-8"],"Date":["Thu, 29 Sep 2022 18:46:33 GMT"],"Content-Length":["42"],"Keep-Alive":["timeout=5"]},"status":429}
{"level":"debug","ts":1664477193.3910909,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"peertube:9000","duration":0.046735959,"request":{"remote_ip":"172.71.150.44","remote_port":"30504","proto":"HTTP/2.0","method":"GET","host":"tube.haddock.cc","uri":"/api/v1/videos?start=0&count=25&sort=-trending&skipCount=true&nsfw=false","headers":{"Sec-Fetch-Mode":["cors"],"Cf-Ray":["7526e1da5b65c381-SEA"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.71.150.44"],"Referer":["https://tube.haddock.cc/"],"Cdn-Loop":["cloudflare"],"Accept-Encoding":["gzip"],"Cf-Connecting-Ip":["63.231.52.232"],"Accept-Language":["en-US,en;q=0.5"],"Cf-Ipcountry":["US"],"Cf-Visitor":["{\"scheme\":\"https\"}"],"If-None-Match":["0.760705541650023"],"Cookie":[],"X-Forwarded-Proto":["https"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0"],"X-Forwarded-Host":["tube.haddock.cc"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"tube.haddock.cc"}},"headers":{"Tk":["N"],"Keep-Alive":["timeout=5"],"X-Frame-Options":["DENY"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Credentials":["true"],"Date":["Thu, 29 Sep 2022 18:46:33 GMT"],"X-Ratelimit-Reset":["1664477198"],"Content-Length":["11"],"X-Ratelimit-Limit":["50"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"b-EFAlOux7Kcr/ZEgGkn2r+oFAbu4\""],"Connection":["keep-alive"],"Access-Control-Expose-Headers":["Retry-After"],"X-Ratelimit-Remaining":["43"]},"status":200}

5. What I already tried:

This seems to be an issue other people have if they don’t use the included nginx webserver for PeerTube. This person had a similar problem, but they were using Apache. Their fix was using

ProxyPreserveHost On
ProxyRequests On

I tried finding an equivalent in Caddy, to no avail. That’s the reason I’m posting this here, instead of the PeerTube forum.

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