1. The problem I’m having:
Note: On this question, I have anonymized all URLs and etc,
Context:
My customer uses a caddy 2.6.3 on his server (https://mygooddomain.com.br), and wants a specific URL /pages/
and every subpath of that url to transparently display to the user the content of an external website (https://sample-site.deno.dev) ( it’s a new CMS he wants to try and migrate the site little by little)
I managed to create a configuration that works only partially and doesn’t suit all cases by using:
handle /pages* {
redir /pages /pages/ permanent
uri strip_prefix /pages/
reverse_proxy * https://sample-site.deno.dev {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Forwarded-Proto {scheme}
}
}
Problem:
The previous configuration works correctly for https://mygooddomain.com.br/pages/
but does not work for https://mygooddomain.com.br/pages/my-subpath
and all subpaths of /pages/
Is there any way to configure this?
That is, I want the routes to look something like this:
https://mygooddomain.com.br
→ Caddy redirects the request to production-a:5000
or production-b:5000
https://mygooddomain.com.br/pages/?q=querystring
→ Caddy shows/redirects-changing-the-url the request to https://sample-site.deno.dev/?q=querystring
https://mygooddomain.com.br/pages/my-subpath?q=querystring
→ Caddy shows/redirects-without-changing-the-url the request to https://sample-site.deno.dev/my-subpath?q=querystring
https://mygooddomain.com.br/pages/my-subpath/?q=querystring
→ Caddy shows/redirects-without-changing-the-url the request to https://sample-site.deno.dev/my-subpath/?q=querystring
https://mygooddomain.com.br/pages/my-subpath/subsubpath?q=querystring
→ Caddy shows/redirects-without-changing-the-url the request to https://sample-site.deno.dev/my-subpath/subsubpath?q=querystring
2. Error messages and/or full log output:
Caddy 2.6.3 generate two log lines:
Note: I had to send a link to gist because the this forum site refused to accept the JSON content even though it was inside a ``` code block
3. Caddy version:
2.6.3
4. How I installed and ran Caddy:
official docker caddy:2.6.3-alpine image
a. System environment:
Ubuntu 22.04
Docker version 23.0.3, build 3e7cbfd
Docker Compose version v2.17.2
b. Command:
PASTE OVER THIS, BETWEEN THE ``` LINES.
Please use the preview pane to ensure it looks nice.
c. Service/unit/compose file:
d. My complete Caddy config:
# See https://caddyserver.com/docs
# Email for Let's Encrypt expiration notices
{
email {$TLS_EMAIL}
# Production directory
# comment the bellow line to allow Caddy uses letsencrypt
# or fallback to ZeroSSL
# acme_ca https://acme-v02.api.letsencrypt.org/directory
# Staging directory - certificates will be invalid
# use this to testing and avoid the rate-limit
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
(security_headers) {
header * {
# enable HSTS
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#strict-transport-security-hsts
Strict-Transport-Security "max-age=3600; includeSubDomains; preload"
# disable clients from sniffing the media type
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options
X-Content-Type-Options "nosniff"
# clickjacking protection
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options
X-Frame-Options "DENY"
# xss protection
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-xss-protection
X-XSS-Protection "1; mode=block"
# Remove -Server header, which is an information leak
# Remove Caddy from Headers
-Server
# keep referrer data off of HTTP connections
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy
Referrer-Policy strict-origin-when-cross-origin
}
}
# "www" redirect to "non-www" version
www.mygooddomain.com.br {
import security_headers
redir https://mygooddomain.com.br{uri}
}
www.staging.mygooddomain.com.br {
import security_headers
redir https://staging.mygooddomain.com.br{uri}
}
(shared_config) {
import security_headers
# Dynamically compress response with gzip when it makes sense.
# This setting is ignored for precompressed files.
encode zstd gzip
# Logs:
log {
output stdout
}
@health_path {
method GET
path /health
}
handle @health_path {
respond "Webserver ok" 200
}
@other_paths {
# controls domains or path that
# the basicauth should not be enforced
not path /health
not host mygooddomain.com.br
}
basicauth @other_paths {
# https://caddyserver.com/docs/caddyfile/directives/basicauth
# ./caddy_linux_amd64 hash-password --plaintext "<REPLACE_BY_YOUR_PASSWORD>"
# DO NOT USE THE HASH BELOW. IT'S A RANDOM GENERATED USED ONLY FOR THIS SAMPLE
mygooddomain $2a$14$UgRcULU9Hgv3f2QfRXV2qOUpq3.WxWs4vKlYetIaiQc3j.CezzkPO
}
tls {
# uncomment force generates a self-signed TLS certificate
# issuer internal
# generated 2023-01-28, Mozilla Guideline v5.6, Caddy 2.1.2, intermediate configuration
# https://ssl-config.mozilla.org/#server=caddy&version=2.1.2&config=intermediate&guideline=5.6
# note that Caddy automatically configures safe TLS settings
protocols tls1.2 tls1.3
ciphers TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_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
}
}
mygooddomain.com.br, staging.mygooddomain.com.br {
import shared_config
@production {
host mygooddomain.com.br
}
@staging {
host staging.mygooddomain.com.br
}
handle /pages* {
redir /pages /pages/ permanent
uri strip_prefix /pages/
reverse_proxy * https://sample-site.deno.dev {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Forwarded-Proto {scheme}
}
}
# Serve static files
handle /static/* {
uri strip_prefix /static
header {
Cache-Control "public, max-age=2592000, must-revalidate"
defer
}
file_server @production {
# STATIC_ROOT for production
root /data/mygooddomain.com.br/static
# Staticfiles are pre-compressed in `start.sh`
precompressed br gzip
}
file_server @staging {
# STATIC_ROOT for production
root /data/staging.mygooddomain.com.br/static
# Staticfiles are pre-compressed in `start.sh`
precompressed br gzip
}
}
# Serve media files
handle /media/* {
uri strip_prefix /media
header {
Cache-Control "private, max-age=2592000, must-revalidate"
defer
}
file_server @production {
# MEDIA_ROOT for production
root /data/mygooddomain.com.br/media
}
file_server @staging {
# MEDIA_ROOT for production
root /data/staging.mygooddomain.com.br/media
}
}
# Serve production app
handle @production {
reverse_proxy production-a:5000 production-b:5000 {
health_headers {
Host localhost
}
health_uri /health/app
health_interval 1s
health_timeout 5s
health_status 200
fail_duration 5s
lb_policy round_robin
header_up Host {host}
header_up X-Real-IP {remote}
}
}
# Serve staging app
handle @staging {
reverse_proxy staging-a:5000 staging-b:5000 {
health_headers {
Host localhost
}
health_uri /health/app
health_interval 1s
health_timeout 5s
health_status 200
fail_duration 5s
lb_policy round_robin
header_up Host {host}
header_up X-Real-IP {remote}
}
}
}