Reverse proxy external https backend with Caddy
In this post, I’ll introduce you to Caddy and guide you through configuring it as a reverse proxy for hosting a Single Page App (SPA).
The post expects that you have a caddy server running with a basic Caddyfile, and will help you configure the SPA and https backend configuration.
Single Page Apps and Backend APIs
Most Single Page Apps rely on a backend API, and sometimes that API is hosted on a different server. If your backend is secured, you might need to send Authorization headers, and the backend server will need to support Cross-Origin Resource Sharing (CORS) headers.
CORS requests involve an additional roundtrip to the server, which can double the request time and increase latency when using your app.
By setting up a reverse proxy, you can “hide” the fact that your backend is hosted on a separate server, improving performance and simplifying your architecture.
A Common Setup
A typical reverse proxy setup might look like this:
/index.html
: This is retrieved directly from Server A 🟢.
/backend/api/posts
: This request is forwarded by Server A to Server B🔵’s /api/posts
route. (Server A then sends the response back to the browser).
In this setup, the browser doesn’t know that it’s actually communicating with a backend hosted on another domain. The browser does not require CORS pre-requests, and its easier to secure your API and website. 😎
What is Caddy?
Caddy is a powerful web server designed to handle a variety of tasks, including serving as a reverse proxy. It’s simple to configure, flexible, and well-suited for modern web applications like SPAs. Caddy fills the same role as other servers such as nginx
or traefik
.
Setting Up Caddy as a Reverse Proxy for SPA
Here’s a basic Caddyfile to configure Caddy as a reverse proxy for an SPA:
# This file was tested with Caddy v2.9
# Basic reverse proxy setup
yourdomain.example.com {
# IMPORTANT: `handle_path` automatically "replaces" the route when forwarding.
# Example: /backend/api/blog/posts will be forwarded to /api/blog/posts.
# If you don't want this replacement behavior, use `handle` instead.
handle_path /backend/* {
reverse_proxy {
to your_backend_url:8000
# If your backend is HTTPS, see the example below for additional configuration.
}
}
# This is the basic SPA hosting setup
handle {
# Root: We assume the SPA files are located in the /srv directory.
root * /srv
# `try_files` will fall back to the index if no matching files are found.
try_files {path} /index.html
# file_server will serve static files from the root path.
file_server
}
}
Modifying for External HTTPS Backends
If your reverse proxy is forwarding requests to an external server hosted on HTTPS, you’ll need to make a small modification to handle this correctly. Specifically, you need to adjust the Host header.
Here’s how to modify your Caddyfile for this scenario:
handle_path /backend/* {
reverse_proxy {
to https://your_backend_url.example.com
# If the target server is external and hosted over HTTPS,
# you need to modify the `Host` header to match the target server's host.
header_up Host {http.reverse_proxy.upstream.hostport}
# This ensures the correct Host header is sent for HTTPS requests.
}
}
This configuration forwards requests to an external server securely, ensuring proper handling of the Host header for HTTPS connections.
Note: The guide was written with Caddy 2.9 in mind at the end of 2024, and may be outdated when you read this. Please check with the official docs if anything does not work as expected. https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas
Conclusion
That’s all you need to set up a reverse proxy with Caddy to host an SPA! If you’re interested in learning how to run Caddy with Docker, stay tuned for a future post on that topic.
For more detailed documentation, visit the official Caddy docs: https://caddyserver.com/docs
// Nils Henrik