The "subfolder problem", OR, "why can't I reverse proxy my app into a subfolder?"

Have you ever tried to reverse proxy an app into its own little subfolder of your domain name?

Makes things neat, doesn’t it? Using example.com/foo/ for one app, example.com/bar/ for another. If you’re coming here from one of the selfhosted communities, you might be thinking along the lines of example.com/sonarr/, example.com/radarr/ etc.

Chances are, you’ve tried some configuration along these lines:

example.com {
  redir /sonarr /sonarr/
  handle_path /sonarr/* {
    reverse_proxy localhost:8989
  }
}

But then disaster strikes, and you get a very poorly formatted page! Maybe links don’t work at all. Maybe you just get redirected straight away to a blank page. Images, style sheets, and scripts don’t work. If you’re struggling with this issue, read on!

Understanding the problem


Firstly, know that this isn’t something unique to Caddy. Every single reverse proxy server you’d care to name will have this inherent problem (unless they have some seriously advanced logic built in to handle it!). That’s because this is a logical problem, not strictly a bug or issue with Caddy or the app itself.

Almost all HTTP applications are written under the assumption that they are serving requests from the web root. A request to example.com/ would be a request for the web root - it just means the highest point in the path hierarchy, the first path element, i.e. not a subfolder.

When you request a website, you usually get a HTML document in response. The HTML directs your browser to additional elements; static assets like images, style sheets, and scripts it uses to further render out the web page. Because it’s assuming it has the web root, it might give your browser links like /images/stock.jpg and /css/site.css. When you open your browser and access your application directly, that’s fine.

But when we put a reverse proxy in the middle, and pen it into a subfolder, we might run into a problem. You’re proxying your application from /foo/. When you send the request to Caddy, and you’re browsing to example.com/foo/, you get a HTML document just like before. But the document still has links to /images/stock.jpg and /css/site.css. Your browser follows those links and makes those requests to Caddy. But Caddy doesn’t know how to handle requests for /images/ or /css/! You’ve only configured the reverse proxy for /foo/. And now our asset links are broken and the page is, too.

Redirects are another problem, as well. They don’t happen in HTML, usually; they happen in HTTP headers (specifically, Location). These, too, are unlikely to properly reference the subfolder you want.

Fixing the problem


Ultimately, to fix this issue, all links and redirects must match up to valid URLs that Caddy knows how to handle. Something’s gotta give; either the HTTP application must correct its URLs to add the subfolder you want, or you need to configure Caddy to let the app have the web root it wants.

Option 1: Upstream

Some apps, like Sonarr, are programmed with the assumption that they might be reverse proxied into a subfolder. There is a helpful option in the application’s settings - commonly referred to as a “URL Base” - which will add your subfolder as a prefix to all of its links/headers. If the app you’re proxying has a feature like this, hurrah! Set it and forget it. If not, this solution isn’t an option.

Option 2: In Caddy

A. By giving up the web root

You can use a subdomain instead, e.g. sonarr.example.com, and proxy the entire web root. This configuration precludes the issue described above and will work for absolutely any HTTP application.

B. By HTML filtering/header rewriting (advanced)

Alternately, you could try to use Caddy as a filtering layer and intercept those incorrect links as they come downstream from your HTTP application.

A Caddy 2 module has been completed and is in the process of being published that can perform replacements in response bodies: GitHub - caddyserver/replace-response: Caddy module that performs replacements in response bodies

Using that plugin involves defining rules that Caddy uses to alter the HTML document it receives from the upstream HTTP application before sending it to the client. In this way, the client would receive the correct links in HTML.

Rewriting headers from upstream can be achieved already using Caddy’s reverse_proxy directive which changes headers only to or from the backend (or, to change any/all headers, you can use the header directive. In this way, you can fix Location redirects as well. Combined, if carefully executed, these two strategies can produce a neatly contained HTTP application inside a subfolder without requiring the app to explicitly support this behaviour.

19 Likes
Radarr/sonarr etc.. without base url
URI rewrite needed
Create a reverse proxy for two ports
Can't get redirects to work via Caddyfile(more info in description)
Caddy 2: KiwiBNC websocket reverse_proxy
[Solved] phpMyAdmin as subdirectory of localhost
Can't make reverse_proxy to play nicely with redirects (minio, netdata, urbit)
How to make reverse_proxy work properly
Migrate multiple sites w/ reverse proxy in v2 (proxy to subdir)
"Classic" subfolder issues? (I read the wiki)
Handle_path does not append prefix to in app urls
Reverse_proxy without base_url / subpath / prefix
Caddy as reverse proxy for docker services
Accessing page using the IP:PORT of the server and preventing redirection to domain.tld/subfolder when accessed by IP
Reverse_proxy with custom header_up
Caddyfile config for reverse proxy using domain subfolder
Caddyfile config for reverse proxy using domain subfolder
Reverse proxy localhost from docker
[SOLVED]Help to "reverse_proxying" two syncthing instances
Simple Caddyfile example map microservice name to port
Overseerr and caddy possible?
Running php-app in a subdirectory causes problems
Help with tomcat reverse proxy
How to route a separate app through the same domain using different path
Rewrite for heimdall dashboard
Asrock ipmi and caddy v2
Https subpath routing issues on azure container instance (docker)
Caddyfile help - Removing subdirectory / subfolder from url
Caddyfile help - Removing subdirectory / subfolder from url
Reverse_proxy header_down on location header... or something equivalent?
Blank Page with reverse proxy
Can not reach the Main Page of my MediaWiki that's behind a reverse proxy server (Caddy). I can only see the Apache2 Default Page
Reverse Proxy with rewrite of path, stripping parts
Caddyfile + docker-compose + https
Help converting nginx config to caddy
Help converting nginx config to caddy
CSS stylesheet not being loaded with reverse proxy and uri strip_prefix
Handle_path not working as expected
Can't access sub-paths in reverse-proxy
URL PATH Rewrite
Errors with v2's reverse_proxy to remote domain
Caddy confusion (concern)
Reverse proxy with path matchers
Can't get Caddy v2 to work on Home Assistant as reverse proxy
Having trouble getting redirect to work
Different redirection
Webpage not rendering fully
How is subfolder proxying progressing?
Websocket Proxy (Phoscon App) - Caddy as NGINX alternative
404 when load css/js use reverse_proxy
General Security concerns. what to do next?
Can I route on remote_ip? or if remote_ip -eq
Full Caddyfile for home server
Combining rewrite and reverse_proxy
How to serve file_server from different path?
Rutorrent + reverse proxy
Change protocol from HTTP/2 to HTTP/1.1?
Jellyfin, Gotify, cadyv2
Port Forwarding for AdGuard Home (Port 80)
React UI that authenticates with Okta and talks over a websocket to a Java backend
.htaccess to caddy v2
Path Matcher not Working on Internal Network Address accessed Directly with IP
Reverse proxy not working for Sonarr
Caddyfile for code-server
Overseerr Reverse Proxy Setup
Routing directive, Reverse Proxy assets got 400
Reverse Proxy and HTTPS for multiple services over Tailscale
qBittorrent and Caddy reverse proxy
Can't access resources which are served as a reverse proxy
Reverse proxy not working as expected
How can i use caddy on other port
[Docker] Reverse-proxy in path for a React containerized app
Cant run Mern app under costum path, only index path works
Log the request before reverse_proxy
Combine multiple webinterfaces to 1 by caddy
Reverse Proxy routing based on directory in URL
Redirection with revers proxy
CFSSL behind caddy reverse proxy with subpath rewrite
Reverse Proxy Ghost blog on Subpath breaking jquery
Redirect port to subdir
Reverse proxy with IP cameras question

A post was split to a new topic: Alias directive like in NGINX?