3 months 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
2 months ago
Railway is routing traffic to the port instead of the staging block
2 months ago
You will have to update the file so the port block handles that case which maybe a catch all
2 months 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