Introduction
Caddy is an open source web server that handles a lot of the tedious parts of running a website automatically, most notably encryption certificates for HTTPS [web:6]. If you have ever built a website on your own computer and had to view it through a plain, unencrypted http://localhost:1313 address, you already know the small friction that comes with local development: no padlock icon, occasional mixed-content warnings, and URLs that do not look anything like what your visitors will eventually see in production.
This guide is written for developers, hobbyists, and static site builders who want to preview their local projects, such as a Hugo site, through a real-looking HTTPS address on their own machine. You do not need any prior experience with reverse proxies, certificates, or command-line servers. By the end of this guide you will understand what Caddy does, why it exists, how to install it, and how to configure it to sit in front of a local Hugo development server.
Think of Caddy as a friendly receptionist standing in front of your website. Instead of visitors (your browser) walking straight into the back office (your Hugo server running on port 1313), they check in with the receptionist first. The receptionist happens to also hand out a security badge (an HTTPS certificate) so that everyone who visits feels like they are in a real, secure building rather than someone’s messy workshop.
Beginner Explanation
At its core, Caddy is a web server, which means its job is to listen for requests coming from a browser and send back a response, usually a web page. What makes Caddy different from older web servers like Apache or Nginx is that it was designed from day one to make HTTPS automatic, rather than something you bolt on afterward [web:6].
HTTPS is the secure version of HTTP, the protocol browsers use to talk to web servers. It encrypts the connection so that nobody snooping on the network can read the traffic. On the public internet, getting an HTTPS certificate usually involves a service called Let’s Encrypt, which verifies that you actually control a domain name before issuing a certificate. Caddy automates this entire process for public sites, and it can also generate its own locally trusted certificates for domains that only exist on your computer, such as localhost or a custom .localhost address [web:13].
The problem Caddy solves in a local development context is straightforward: it lets your local site behave like a real website. Instead of typing http://localhost:1313 into your browser, you can visit something like https://mysite.localhost and get a clean, secure, and readable address, backed by a trusted certificate that your browser will not complain about [web:13].
In a bigger picture, Caddy commonly plays the role of what is called a “reverse proxy.” A reverse proxy is a server that sits between the outside world and one or more backend applications, forwarding requests to the right place. This is a common pattern in production infrastructure, and using it locally gives you a taste of how real deployments are structured, while keeping the setup simple enough for a single laptop [web:3].
How It Works
When you run Caddy with a configuration file, called a Caddyfile, it starts listening on your machine for incoming connections. When your browser requests a page, Caddy receives that request first, decides where it needs to go based on the rules in the Caddyfile, and then forwards (“proxies”) it to the real backend, in this case your local Hugo server [web:3].
Here is the basic flow when you visit a local Hugo site through Caddy:
Browser (https://localhost)
|
v
Caddy (reverse proxy, handles HTTPS)
|
v
Hugo Development Server (localhost:1313)
The Hugo server itself has no idea Caddy is involved. It just serves plain HTTP on its own port, exactly as it always has [web:17]. Caddy is the layer that adds the HTTPS wrapper and the friendlier address on top.
Three pieces make this system work together:
- Caddy binary: the actual program that reads your Caddyfile and runs the server [web:6]
- Caddyfile: a plain text configuration file describing which domain maps to which backend
- Hugo server: the local development server built into Hugo that rebuilds and serves your site on file changes [web:17]
Caddy’s automatic HTTPS feature detects when a hostname looks like a real domain versus a purely local one, and it adjusts its certificate strategy accordingly, using locally trusted certificates for things like .localhost addresses rather than trying to reach out to Let’s Encrypt [web:4][web:13].
Step-by-Step Tutorial
This tutorial assumes you already have a Hugo site set up in a project folder, and that you have basic comfort with a terminal.
Prerequisites
Before starting, make sure you have:
- A working Hugo site (any theme, even the default starter site works)
- Terminal or command-line access on macOS, Windows, or Linux
- Administrator or sudo access, needed for some install steps
Step 1: Install Caddy
The installation command depends on your operating system.
On macOS with Homebrew:
brew install caddy
On Linux (Debian/Ubuntu-based):
sudo apt-get update && sudo apt-get install caddy -y
On Windows, download the binary from the official Caddy downloads page and place it somewhere in your PATH, or install via a package manager like winget [web:5].
Once installed, verify it worked:
caddy version
Expected output looks something like:
v2.8.4 h1:...
[Screenshot: Terminal window showing the output of “caddy version” with a version number displayed]
Caption: Confirm that a version string is printed, which proves Caddy installed correctly and is available on your PATH.
Step 2: Create a Caddyfile
In the root of your Hugo project (or any convenient folder), create a plain text file named Caddyfile with no file extension:
localhost {
reverse_proxy localhost:1313
}
This tells Caddy: “When someone requests localhost, forward that request to whatever is running on port 1313,” which is Hugo’s default development server port [web:17].
If you would prefer a friendlier custom address instead of plain localhost, you can use a .localhost subdomain, since modern browsers and operating systems resolve any *.localhost name to your own machine without extra configuration [web:13]:
mysite.localhost {
reverse_proxy localhost:1313
}
Step 3: Start the Hugo Server
In your project folder, start Hugo’s built-in development server:
hugo server
Expected output includes a line similar to:
Web Server is available at http://localhost:1313/
Leave this terminal window running. Hugo will watch your files and rebuild the site automatically when you make changes [web:17].
[Screenshot: Terminal output showing Hugo server started and listening on port 1313]
Caption: This confirms Hugo is serving your site directly, before Caddy is placed in front of it.
Step 4: Start Caddy
Open a second terminal window in the same folder as your Caddyfile and run:
caddy run
Caddy will read the Caddyfile, obtain or generate the appropriate certificate, and start listening for requests [web:3].
Step 5: Verify in the Browser
Open your browser and visit:
https://localhost
or, if you used the custom domain example:
https://mysite.localhost
[Screenshot: Browser window showing the Hugo site loaded over HTTPS with a padlock icon in the address bar]
Caption: Notice the padlock icon, this indicates Caddy successfully served the page over a trusted local HTTPS connection.
If everything is working, you should see your Hugo site rendered normally, but now served securely through Caddy rather than directly from Hugo’s plain HTTP server.
Configuration Examples
A more advanced Caddyfile can route multiple paths to different backends, similar to a small production setup. Here is an example inspired by common community configurations [web:16]:
mysite.localhost {
reverse_proxy /api/* localhost:5000
reverse_proxy localhost:1313
}
This routes anything starting with /api/ to a separate backend service on port 5000, while everything else goes to Hugo on port 1313.
If you prefer running things in containers, a simple Docker Compose setup pairing Caddy with a static site container might look like this:
version: "3.8"
services:
caddy:
image: caddy:latest
ports:
- "443:443"
- "80:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
The volume mount makes sure Caddy inside the container reads the same Caddyfile you configured on your host machine.
Real-World Examples
For a solo developer building a personal blog with Hugo, Caddy is mostly a convenience: it makes local previews feel closer to the final deployed site, and it is a gentle introduction to reverse proxy concepts without any real production risk.
For a small team building a multi-service application, such as a marketing site plus a separate API backend, Caddy running locally can mirror the same routing rules used in staging or production, which reduces “it worked on my machine” surprises later [web:3].
In production scenarios, Caddy is frequently chosen specifically because of its automatic HTTPS handling with real domains and Let’s Encrypt, removing the operational burden of manually renewing certificates [web:6]. Learning the local development workflow first is a low-risk way to become comfortable with the same tool before trusting it with a live domain.
Advantages
- Automatic HTTPS, even for local domains, without manually generating self-signed certificates [web:13]
- Simple, human-readable configuration file format compared to more verbose alternatives
- Same tool can be used locally and in production, reducing the number of new concepts to learn later
- Reverse proxy support makes it easy to combine multiple backend services under one address [web:3]
Disadvantages
- Adds an extra process to manage during local development, which can feel unnecessary for very small projects
- Some
.locor custom domain setups still require additional tools like mkcert for full certificate trust [web:16] - Slightly more moving parts to troubleshoot compared to visiting a plain
http://localhost:1313address directly
Common Mistakes
Forgetting to start Hugo before Caddy. Caddy will accept the request but fail to reach the backend, resulting in a connection error, because there is nothing listening on port 1313 yet. Always start the backend server first, then start Caddy.
Using caddy reverse-proxy on the command line while expecting Caddyfile settings to apply. The caddy reverse-proxy command does not read your Caddyfile at all, it is a separate quick-start shortcut [web:5]. If you have a Caddyfile, use caddy run instead.
Assuming any custom domain gets free automatic HTTPS. Caddy can only fetch a publicly trusted certificate from Let’s Encrypt if the domain’s DNS actually points to your machine and ports 80/443 are reachable from the internet [web:3]. For purely local domains, use .localhost or a tool like mkcert instead [web:16].
Not reloading Caddy after editing the Caddyfile. Configuration changes do not take effect automatically while Caddy is already running; you need to reload or restart the process after saving changes [web:3].
Troubleshooting Guide
| Problem | Possible Cause | Solution |
|---|---|---|
| Browser shows “connection refused” | Hugo server is not running on port 1313 | Start Hugo with hugo server before starting Caddy [web:17] |
| Browser shows certificate warning | Using a non-.localhost custom domain without local certificate setup |
Switch to a .localhost domain or generate a certificate with mkcert [web:13][web:16] |
| Caddy fails to bind to port 443 | Missing permission to bind to low-numbered ports | Run Caddy with elevated permissions or grant the binding capability [web:3] |
| Changes to Caddyfile have no effect | Caddy is still running the old configuration | Stop Caddy and rerun caddy run, or reload the configuration [web:3] |
| Public domain won’t get a certificate | DNS not pointing to your machine, or ports 80/443 blocked | Verify DNS records and open the required ports [web:3] |
Comparison Section
| Approach | Setup Effort | HTTPS Handling | Best For |
|---|---|---|---|
Plain hugo server only |
Minimal | None (plain HTTP) | Quick, throwaway local previews |
| Caddy in front of Hugo | Low | Automatic, including local domains | Realistic local previews, learning reverse proxy concepts [web:13] |
| Nginx reverse proxy | Moderate to high | Manual certificate configuration required | Teams already standardized on Nginx in production |
mkcert plus custom .loc domain |
Moderate | Manual but fully trusted locally | Projects needing custom, non-.localhost domain names [web:16] |
Choose plain Hugo for the simplest possible workflow, Caddy when you want a low-effort taste of production-like routing and HTTPS, and Nginx or mkcert-based setups when your team’s production environment already relies on those tools specifically.
Frequently Asked Questions
Do I need Caddy to run a Hugo site locally? No, Hugo’s built-in server works fine on its own. Caddy is optional and mainly useful if you want HTTPS or reverse proxy behavior locally [web:17].
Is HTTPS actually necessary for local development? Not strictly, since there is no real network transit happening on localhost, but it can help catch mixed-content bugs early and make local testing feel closer to production [web:16].
Can I use a custom domain name instead of localhost?
Yes, .localhost subdomains work out of the box in most browsers, and tools like mkcert let you use other custom local domain names with trusted certificates [web:13][web:16].
Why did Caddy fail to get a certificate for my custom domain? Public certificate issuance through Let’s Encrypt requires your domain’s DNS to point to your machine and ports 80/443 to be reachable, which is usually not true for local-only domains [web:3].
What is the difference between caddy run and caddy reverse-proxy?
caddy run reads your Caddyfile for full configuration flexibility, while caddy reverse-proxy is a quick command-line shortcut that ignores any Caddyfile [web:5].
Does Caddy work on Windows? Yes, Caddy is cross-platform and can be installed via a downloaded binary or a package manager like winget [web:5].
Can Caddy proxy to more than one backend service? Yes, using path-based matching in the Caddyfile, you can route different URL paths to different backend servers [web:3].
Is Caddy suitable for production, or only local development? Caddy is widely used in production specifically because of its automatic HTTPS handling with real domains, not just for local development [web:6].
Do I need to restart Caddy every time I edit my Hugo content? No, only changes to the Caddyfile itself require a Caddy restart or reload; Hugo’s own server handles content changes automatically [web:3][web:17].
Can Caddy run inside Docker for local development? Yes, Caddy has an official Docker image, and mounting your Caddyfile into the container is a common setup pattern.
Related Resources
- Official Documentation: https://caddyserver.com/docs/
- Reverse Proxy Quick Start: https://caddyserver.com/docs/quick-starts/reverse-proxy
- Automatic HTTPS Documentation: https://caddyserver.com/docs/automatic-https
- Hugo Server Command Reference: https://gohugo.io/commands/hugo_server/
- Community Forum: https://caddy.community/
Further Reading
Readers who want to go further after this guide should look into how Caddy handles wildcard certificates for subdomains, how to deploy Caddy as a production reverse proxy for multiple real domains, and how tools like mkcert complement Caddy for fully custom local domain names [web:8][web:16]. Understanding Hugo’s own build and deployment pipeline is also a natural next step once local previewing feels solid [web:17].
Editorial Notes
Purpose: This guide introduces Caddy as a local reverse proxy for Hugo development, focusing on automatic HTTPS and basic routing concepts for beginners.
Audience: Developers and hobbyists with basic terminal familiarity who are new to reverse proxies and local HTTPS setups.
Scope: Covers installation, a basic Caddyfile, running Caddy alongside Hugo, and common troubleshooting. Does not cover advanced production deployment, load balancing, or wildcard certificate management in depth.
Update History
July 2026
- Initial publication.
- Added installation instructions for macOS, Linux, and Windows.
- Added troubleshooting and comparison sections.