1. The problem I’m having:
I am using caddy to proxy to an emby server (and provide ssl certs). I have a working config except for websockets. Emby uses websockets to show items such as play status / current runtime in the admin dashboard. These work for around a minute before it appears to close the connection.
i have to remove the following to get the websocket connection to work
header_up Connection "upgrade"
header_up Upgrade {>Upgrade}
2. Error messages and/or full log output:
No
3. Caddy version:
4. How I installed and ran Caddy:
Installed by downloading the caddy linux amd64 package from the download page and installing with dpkg -i
a. System environment:
uname -a
Linux LDN-GW-LAN 6.1.0-31-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.128-1 (2025-02-07) x86_64 GNU/Linux
b. Command:
c. Service/unit/compose file:
[Unit]
Description=Caddy - A Fast, Portable, and Automated Web Server
Documentation=https://caddyserver.com/docs/
After=network.target
[Service]
User=caddy
Group=caddy
ExecStart=/bin/bash -c 'set -a; source /etc/caddy/cloudflare.env; exec /usr/bin/caddy run --config /etc/caddy/Caddyfile'
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
Restart=on-failure
TimeoutStopSec=5s
# Load Environment Variables for Cloudflare DNS API Token
Environment="CADDY_DATA_DIR=/var/lib/caddy"
Environment="CADDY_CONFIG_DIR=/etc/caddy"
EnvironmentFile=/etc/caddy/cloudflare.env
# Ensure the service has the correct working directory
WorkingDirectory=/var/lib/caddy
# File and process limits
LimitNOFILE=1048576
LimitNPROC=512
# Hardening security (restrict access)
AmbientCapabilities=CAP_NET_BIND_SERVICE
ProtectHome=true
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
d. My complete Caddy config:
### Global Configuration ###
{
servers {
protocols h1 h2 h3
strict_sni_host
keepalive_interval 20s
}
# Enable wildcard preference for certificates
auto_https prefer_wildcard
}
### Load Cloudflare API Tokens and Emails from Environment File ###
(cloudflare_tls_domain_co_uk) {
tls {
protocols tls1.2 tls1.3
curves x25519 secp384r1 secp256r1
ciphers TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 \
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 \
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 \
TLS_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
dns cloudflare {env.CLOUDFLARE_API_TOKEN_domain_co_uk}
key_type p256
}
}
(cloudflare_tls_domain_xyz) {
tls {
protocols tls1.2 tls1.3
curves x25519 secp384r1 secp256r1
ciphers TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 \
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 \
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 \
TLS_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
dns cloudflare {env.CLOUDFLARE_API_TOKEN_domain_xyz}
key_type p256
}
}
### Security Headers ###
(x_frame_options) {
header X-Frame-Options "SAMEORIGIN"
}
(strict_transport_security) {
header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
}
(content_security_policy) {
header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';"
}
(permissions_policy) {
header Permissions-Policy "camera=(), microphone=(), geolocation=()"
}
(referrer_policy) {
header Referrer-Policy "strict-origin-when-cross-origin"
}
(x_content_type_options) {
header X-Content-Type-Options "nosniff"
}
(minimal_security) {
import referrer_policy
}
(basic_security) {
import x_content_type_options
import referrer_policy
import permissions_policy
}
(strict_security) {
import strict_transport_security
import x_frame_options
import x_content_type_options
import referrer_policy
import permissions_policy
}
### App Specific CSP's ###
(emby_csp) {
header Content-Security-Policy "
default-src 'self' https://mb3admin.com https://emby.domain.xyz;
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
frame-ancestors 'self';
connect-src 'self' wss: https://mb3admin.com;
media-src 'self' https://emby.domain.xyz;
"
}
### Wildcard Certificate for *.domain.co.uk ###
*.domain.co.uk {
import cloudflare_tls_domain_co_uk
}
### Wildcard Certificate for *.domain.xyz ###
*.domain.xyz {
import cloudflare_tls_domain_xyz
}
### Reverse Proxy for Emby ###
emby.domain.xyz {
import strict_security
import emby_csp
encode gzip zstd
# Main Reverse Proxy for Emby
reverse_proxy http://172.18.50.20:8096 {
transport http {
keepalive 60s
dial_timeout 15s
response_header_timeout 30s
expect_continue_timeout 5s
}
flush_interval 1s
# Ensure Proper Forwarding of Client IP & Headers
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up Host {host}
# Disable Proxy Buffering (Fixes Streaming Issues)
header_down Cache-Control "no-cache, no-store, must-revalidate"
}
# General access log (INFO level for all requests)
log {
output file /var/log/caddy/emby_access.log {
roll true
roll_size_mb 10
roll_gzip true
roll_local_time
roll_keep 3
roll_keep_days 7
}
level INFO
}
# Error log (Only logs errors)
log {
output file /var/log/caddy/emby_error.log {
roll true
roll_size_mb 5
roll_gzip true
roll_local_time
roll_keep 5
roll_keep_days 14
}
level ERROR
}
@http {
protocol http
}
redir @http https://{host}{uri} permanent
}