1. The problem I’m having:
I use two Caddy servers, one is a server for nextcloud-fpm
and another works as a reverse proxy onto the first. If I connect to nextcloud via the reverse proxy, some images are reloaded on every refresh, same as in this reddit thread. Note how the cloudy background image gets reloaded every time.
If I instead connect directly to the nextcloud server caddy OR if I go via reverse proxy, but in a “private browsing” tab, the problem doesn’t occur.
I’m seeing this behavior both in firefox and chrome.
2. Error messages and/or full log output:
I’m not sure which logs/under which circumstances might be useful.
3. Caddy version:
reverse proxy:
v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
server:
v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
4. How I installed and ran Caddy:
a. System environment:
Docker
b. Command:
docker compose up -d
c. Service/unit/compose file:
reverse-proxy:
services:
reverse-proxy:
container_name: reverse-proxy
image: caddy:alpine
restart: unless-stopped
ports:
- 80:80
- 443:443
security_opt:
- no-new-privileges
environment:
- TZ=Europe/Berlin
networks:
- reverse_proxy
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./caddy/data:/data
- ./caddy/config:/config
- ./certs:/certs
networks:
reverse_proxy:
external: true
nextcloud:
secrets:
nextcloud_db_passwd:
file: ./nextcloud_db_passwd
nextcloud_redis_passwd:
file: ./nextcloud_redis_passwd
nextcloud_admin_passwd:
file: ./nextcloud_admin_passwd
services:
db:
container_name: nc_db
image: postgres:17-alpine
restart: unless-stopped
security_opt:
- no-new-privileges
secrets:
- nextcloud_db_passwd
healthcheck:
test: ["CMD-SHELL", "pg_isready -d postgres -U $${POSTGRES_USER}"]
start_period: 10s
interval: 30s
retries: 5
timeout: 5s
environment:
- TZ=Europe/Berlin
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD_FILE=/run/secrets/nextcloud_db_passwd
networks:
- internal
volumes:
- ./dbdata:/var/lib/postgresql/data
redis:
container_name: nc_redis
image: docker.io/valkey/valkey:8-alpine
restart: unless-stopped
security_opt:
- no-new-privileges
secrets:
- nextcloud_redis_passwd
healthcheck:
test: ["CMD-SHELL", "redis-cli", "-a", "$$(cat /run/secrets/paperless_redis_passwd)", "--raw", "incr", "ping"]
start_period: 10s
interval: 30s
retries: 5
timeout: 3s
environment:
- TZ=Europe/Berlin
command: sh -c 'valkey-server --requirepass "$$(cat /run/secrets/nextcloud_redis_passwd)"'
networks:
- internal
server:
container_name: nc_server
image: caddy:alpine
restart: unless-stopped
networks:
- reverse_proxy
- internal
security_opt:
- no-new-privileges
environment:
- TZ=Europe/Berlin
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./caddy/data:/data
- ./caddy/config:/config
- ./ncdata:/var/www/html
- /mnt/md0/ncuserdata:/var/www/html/data
depends_on:
- nextcloud
nextcloud:
container_name: nc_app
image: nextcloud:stable-fpm-alpine
restart: unless-stopped
user: 82:82
security_opt:
- no-new-privileges
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
secrets:
- nextcloud_db_passwd
- nextcloud_redis_passwd
- nextcloud_admin_passwd
environment:
- TZ=Europe/Berlin
- REDIS_HOST=redis
- REDIS_HOST_PASSWORD_FILE=/run/secrets/nextcloud_redis_passwd
- POSTGRES_HOST=db
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD_FILE=/run/secrets/nextcloud_db_passwd
- NEXTCLOUD_ADMIN_USER=s
- NEXTCLOUD_ADMIN_PASSWORD_FILE=/run/secrets/nextcloud_admin_passwd
- NEXTCLOUD_TRUSTED_DOMAINS=nc_server nc.local # nc is for selfconnect
- TRUSTED_PROXIES=172.19.0.0/24 172.27.0.0/24
networks:
internal:
ipv4_address: 172.21.0.10 # for ufw rule
volumes:
- ./ncdata:/var/www/html
- /mnt/md0/ncuserdata:/var/www/html/data
- ./nextcloud-php-fpm.conf:/usr/local/etc/php-fpm.d/zzz-custom.conf:ro # php-fpm custom conf
networks:
internal:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
gateway: 172.21.0.1
reverse_proxy:
external: true
d. My complete Caddy config:
reverse-proxy:
{
# log {
# level DEBUG
# output file /var/log/access.log
# }
}
nc.local {
tls /certs/nc.local.crt /certs/nc.local.key
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
reverse_proxy nc_server:443 {
transport http {
tls_insecure_skip_verify # because local cert
}
}
}
nc_server:
{
# log {
# level DEBUG
# output file /var/log/access.log
# }
}
(nc) {
request_body {
max_size 10G
}
# Enable gzip but do not remove ETag headers
encode {
zstd # 'fastest', 'better', 'best', 'default'
gzip
minimum_length 256
match {
header Content-Type application/atom+xml
header Content-Type application/javascript
header Content-Type application/json
header Content-Type application/ld+json
header Content-Type application/manifest+json
header Content-Type application/rss+xml
header Content-Type application/vnd.geo+json
header Content-Type application/vnd.ms-fontobject
header Content-Type application/wasm
header Content-Type application/x-font-ttf
header Content-Type application/x-web-app-manifest+json
header Content-Type application/xhtml+xml
header Content-Type application/xml
header Content-Type font/opentype
header Content-Type image/bmp
header Content-Type image/svg+xml
header Content-Type image/x-icon
header Content-Type text/cache-manifest
header Content-Type text/css
header Content-Type text/plain
header Content-Type text/vcard
header Content-Type text/vnd.rim.location.xloc
header Content-Type text/vtt
header Content-Type text/x-component
header Content-Type text/x-cross-domain-policy
}
}
header {
Strict-Transport-Security "max-age=15768000; includeSubDomains"
Referrer-Policy no-referrer
X-Content-Type-Options nosniff
X-Download-Options noopen
X-Frame-Options SAMEORIGIN
X-Permitted-Cross-Domain-Policies none
X-Robots-Tag noindex,nofollow
X-XSS-Protection "1; mode=block"
}
root * /var/www/html
route {
route /robots.txt {
log_skip
file_server
}
# Add exception for `/.well-known` so that clients can still access it
# despite the existence of the `error @internal 404` rule which would
# otherwise handle requests for `/.well-known` below
route /.well-known/* {
redir /.well-known/carddav /remote.php/dav/ permanent
redir /.well-known/caldav /remote.php/dav/ permanent
@well-known-static path \
/.well-known/acme-challenge /.well-known/acme-challenge/* \
/.well-known/pki-validation /.well-known/pki-validation/*
route @well-known-static {
try_files {path} {path}/ =404
file_server
}
redir * /index.php{path} permanent
}
@internal path \
/build /build/* \
/tests /tests/* \
/config /config/* \
/lib /lib/* \
/3rdparty /3rdparty/* \
/templates /templates/* \
/data /data/* \
\
/.* \
/autotest* \
/occ* \
/issue* \
/indie* \
/db_* \
/console*
error @internal 404
@assets {
path *.css *.js *.mjs *.svg *.gif *.png *.jpg *.jpeg *.webp *.ico *.wasm *.tflite *.woff2
file {path} # Only if requested file exists on disk, otherwise /index.php will take care of it
}
route @assets {
header /* Cache-Control "max-age=15552000" # Cache-Control policy borrowed from `.htaccess`
header /*.woff2 Cache-Control "max-age=604800" # Cache-Control policy borrowed from `.htaccess`
log_skip # Optional: Don't log access to assets
file_server {
precompressed gzip
}
}
# Rule borrowed from `.htaccess`
redir /remote/* /remote.php{path} permanent
# required for legacy support
@notlegacy {
path *.php *.php/
not path /index*
not path /remote*
not path /public*
not path /cron*
not path /core/ajax/update*
not path /status*
not path /ocs/v1*
not path /ocs/v2*
not path /ocs-provider/*
not path /updater/*
not path */richdocumentscode/proxy*
}
rewrite @notlegacy /index.php{uri}
# Serve found static files, continuing to the PHP default handler below if not found
try_files {path} {path}/
@notphpordir not path /*.php /*.php/* / /*/
file_server @notphpordir {
pass_thru
}
# Let everything else be handled by the PHP-FPM component
php_fastcgi nextcloud:9000 {
env modHeadersAvailable true # Avoid sending the security headers twice
env front_controller_active true # Enable pretty urls
}
}
}
:80 {
import nc
}
:443 {
tls internal {
on_demand
}
import nc
}