a year ago
I have a web application deployed on Railway with two environments:
- Production: Publicly accessible without authentication
- Staging: Should be protected with HTTP Basic Authentication
I've implemented Caddy as a reverse proxy to handle routing and, in the case of the staging environment, to add Basic Authentication. While the authentication works perfectly in my local environment, it appears to be bypassed in the Railway-hosted staging environment.
What Works
- Local Environment: When testing locally, the Basic Authentication works perfectly
- Requests to test.localhost return 401 without credentials
- Requests with valid credentials are authenticated properly
- The staging.localhost route is also properly protected
- Production on Railway: Working as expected without authentication
What Doesn't Work
Staging on Railway: The Basic Authentication is bypassed
- Requests to staging.example.com return content without requiring authentication
- The response headers show server: railway-edge indicating Railway's edge network is handling the requests
- Authentication headers are being sent but not enforced
Here is my caddy_file:
{ # global options
admin off
persist_config off
auto_https off
log {
format console
level DEBUG
}
servers {
trusted_proxies static private_ranges
}
}
:{$PORT} {
log {
format console
level DEBUG
}
handle /join-waitlist/api/* {
uri strip_prefix /join-waitlist
reverse_proxy {$BACKEND_HOST}
}
handle /api/* {
reverse_proxy {$BACKEND_HOST}
}
header {
# Set JavaScript MIME type for module scripts
Content-Type "application/javascript" *.js
# Set CSS MIME type
Content-Type "text/css" *.css
}
handle_path /join-waitlist/* {
reverse_proxy {$FRONTEND_HOST}
}
redir / /join-waitlist/ permanent
}
# Production staging domain (protected with basic auth)
staging.example.com {
log {
output stdout
format console
}
# Authentication for all paths
basicauth /* {
dev <HASHED_PASSWORD_HERE>
}
# Add cache control headers to prevent edge caching
header {
Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate"
Pragma "no-cache"
Expires "0"
}
# Handle specific paths AFTER authentication
handle /join-waitlist/api/* {
uri strip_prefix /join-waitlist
reverse_proxy {$BACKEND_HOST}
}
handle /api/* {
reverse_proxy {$BACKEND_HOST}
}
header {
Content-Type "application/javascript" *.js
Content-Type "text/css" *.css
}
handle_path /join-waitlist/* {
reverse_proxy {$FRONTEND_HOST}
}
redir / /join-waitlist/ permanent
}
# Local staging environment (also protected)
staging.localhost.test:80 {
log {
output stdout
format console
}
# Same authentication block as production staging
basicauth /* {
dev <HASHED_PASSWORD_HERE>
}
# Handle specific paths AFTER authentication
handle /join-waitlist/api/* {
uri strip_prefix /join-waitlist
reverse_proxy {$BACKEND_HOST}
}
handle /api/* {
reverse_proxy {$BACKEND_HOST}
}
header {
Content-Type "application/javascript" *.js
Content-Type "text/css" *.css
}
handle_path /join-waitlist/* {
reverse_proxy {$FRONTEND_HOST}
}
redir / /join-waitlist/ permanent
}
# Test endpoint to verify authentication
test.localhost.test:80 {
basicauth /* {
dev <HASHED_PASSWORD_HERE>
}
respond "Authentication successful"
} 3 Replies
a year ago
Railway is routing traffic to the port instead of the staging block
a year ago
You will have to update the file so the port block handles that case which maybe a catch all
a year ago
Why don't try logging something in that block and if it is logged when you make a staging request then that will prove this theory