Serve (available) static content and proxy otherwise

Situation

I run Caddy as a frontend proxy to varnish and as a backend proxy to php-fpm.

Caddy > Varnish > Caddy > PHP-FPM

Currently Caddy proxies everything to Varnish. My goal is to have Caddy serve static content from disk directly and use Varnish only to cache heavy PHP requests. Everything runs on the same VPS so using Varnish for static content does not benefit me very much here. I did some Googling but could not find a clear and concise answer. After some trial and error, I figured I could (abuse) the handle_errors directive for this.

Is the following configuration the recommended way to achieve what I want? It seems to work, but I’d rather be sure than run into issues in the long term.

Caddyfile

domain.tld {
  root * /srv/web

  encode zstd gzip

  file_server

  handle_errors {
    reverse_proxy {
      to localhost:8080
      header_up x-real-ip {remote_host}
      header_up x-forwarded-port 443
      header_up x-forwarded-host {host}
      transport http {
        dial_timeout 2s
        keepalive 60s
      }
    }
  }

  log {
    format json
  }
}

:8081 {
  bind localhost

  root * /srv/web

  encode zstd gzip

  php_fastcgi localhost:9000 {
    try_files {path} /app.php{path}?{query} =404
  }

  log {
    format json
  }
}

You can use the file matcher to check if a request maps to a file on disk, before trying to proxy.

root * /srv/web

@exists file
handle @exists {
	file_server
}

handle {
	reverse_proxy localhost:8000 {
		...
	}
}
1 Like

Oh I see. That reads more self-explanatory than my approach. Thanks for sharing!

1 Like

Just for fun, if you use JSON (we haven’t exposed this in Caddyfile… yet?) the file server has a pass_thru option that will go on to the next handler if the file isn’t found: Modules - Caddy Documentation

But yeah I think what Francis posted is the bet way to do it.

Yeah, I guess we haven’t. I’ll do that now.

1 Like

We have similar deployment requirements in some environment:

Client -> Caddy -> Backend
             `---> Frontend (SPA)
  • The frontend and backend share the same domain
  • To achieve this, every request to the backend has a path prefix of /api
  • Then every request else will go to the frontend
  • In particular, since the frontend is an SPA, an additional requirement is that non-existent static files should be replaced with /index.html

And this is the final Caddyfile we have figured out:

example.com {
	route /api/* {
		uri strip_prefix /api
		reverse_proxy localhost:9000
	}

	route {
		root * /home/user/public_html
		try_files {path} /index.html
		file_server
	}
}
1 Like

@RussellLuo you can simplify that a little with handle_path and handle:

example.com {
	handle_path /api/* {
		reverse_proxy localhost:9000
	}

	handle {
		root * /home/user/public_html
		try_files {path} /index.html
		file_server
	}
}
2 Likes

Wow, that’s more concise! Thanks for sharing, Francis :grinning:

This topic was automatically closed after 30 days. New replies are no longer allowed.