When I set up a service on my own server or prepare a backend for a side project, one of the most time-consuming tasks for years has been obtaining and properly configuring an HTTPS certificate. While getting a Let’s Encrypt certificate with certbot is easy, integrating it with the web server, setting up automatic renewal, and sometimes managing it for different domain names, especially on a small VPS, could be quite a hassle. Fortunately, modern HTTP servers like Caddy automate this process with a single command or a simple configuration file. In this post, I’ll explain how you can get a certified self-hosted application up and running in 5 minutes with Caddy.
Caddy, as a modern web server and reverse proxy, offers unparalleled ease of use, especially in automatic HTTPS management. Unlike traditional web servers, Caddy provides free and automatic HTTPS certificates via Let’s Encrypt by default and renews them on its own. This way, we avoid the security vulnerabilities and renewal issues that come with manual certificate management.
What is Automatic HTTPS with a Single Command, and Why Caddy?
Automatic HTTPS is the process by which a web server or proxy automatically obtains, installs, and periodically renews security certificates (SSL/TLS) for a domain name. This eliminates the complexity of obtaining and managing certificates through traditional methods. In my own systems, especially when deploying a new service or changing the domain name of an existing application, I’ve repeatedly experienced how frustrating certificate management can be.
Caddy is one of the most important tools that simplifies this process. Unlike established alternatives like Nginx or Apache, Caddy comes with Let’s Encrypt integration by default. As soon as you define a domain name, it automatically requests a certificate, performs verification, and installs the certificate on your server. This provides an incredible time saving, especially for small and medium-sized projects, personal websites, or internal services. It also completely automates routine but easily forgotten tasks like certificate renewal, saving us a significant burden.
Caddy Installation: How Do We Integrate It into Our System?
Installing Caddy on your server is quite simple. It’s easily installable on most Linux distributions through official package repositories or custom scripts. I usually prefer package managers like apt or dnf because they also bring systemd integration, allowing me to manage Caddy as a service.
As a first step, you should add the package to your system by following the instructions on Caddy’s official website. For example, on a Debian-based system (like Ubuntu), we can use the following commands:
sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Once the installation is complete, Caddy is automatically started as a systemd service and runs under the caddy user. You can check the service status using:
sudo systemctl status caddy
If the service is not running or there’s an error, checking the logs with journalctl -u caddy is a good starting point. Typically, a firewall rule (ports 80 and 443 not being open) or an incorrect Caddyfile configuration causes such issues. I encountered a similar problem in the test environment for my side project; I forgot to check the ufw rules, and Caddy couldn’t perform the Let’s Encrypt verification.
Basic Caddyfile Configuration: How Do I Publish My First Service?
Caddy’s power lies in its simple and readable Caddyfile configuration. This file defines how your web server will behave and which domain names it will serve. Caddy’s default configuration directory is /etc/caddy, and the main configuration file is located at /etc/caddy/Caddyfile.
When we want to expose an internal application or a static website to the internet, it’s enough to just write the domain name and the reverse proxy target in the Caddyfile. For example, let’s assume I want to serve an application running on localhost:8000 on my server through the domain my-app.example.com:
# /etc/caddy/Caddyfile
my-app.example.com {
reverse_proxy localhost:8000
}
That’s it! When Caddy sees this configuration, it will automatically try to obtain a Let’s Encrypt certificate for my-app.example.com. To verify that the domain name is indeed under your control, it typically uses the HTTP-01 challenge method; meaning, it verifies the domain name by accessing a specific file over port 80. Therefore, it’s critical that ports 80 and 443 are accessible from the outside.
After saving the configuration, you need to reload the Caddy service:
sudo systemctl reload caddy
This command tells Caddy to read and apply the new configuration. If there’s an error, Caddy usually refuses to reload the service and writes the error to the logs, allowing you to easily identify the problem. This simple approach has been very useful for me, especially in rapid prototyping and test environments.
Advanced Caddyfile Scenarios: Managing Multiple Domain Names and Subdomains
Hosting multiple web applications or subdomains on a single server is quite common in self-hosting scenarios. Caddy allows us to manage this situation very elegantly within the Caddyfile. By defining a separate block for each domain name or subdomain, we can enable Caddy to obtain individual HTTPS certificates for each and route traffic to the correct destinations.
For example, we might want to host a static website at example.com, serve a backend service at api.example.com, and run a management panel at dashboard.example.com. The Caddyfile would look like this in that case:
# /etc/caddy/Caddyfile
example.com {
root * /var/www/html/static_site
file_server
# Redirect from HTTP to HTTPS is automatic, but can be specified explicitly
# This is the default behavior, so it's usually not written
# redir / https://example.com{uri}
}
api.example.com {
reverse_proxy localhost:8080
header {
# We can add security headers
Strict-Transport-Security max-age=31536000; includeSubDomains preload
X-Frame-Options DENY
X-Content-Type-Options nosniff
}
}
dashboard.example.com {
reverse_proxy localhost:3000
log {
output file /var/log/caddy/dashboard_access.log
format json
}
}
In this configuration, each domain name is defined in its own block. While serving static files for example.com, we are routing the other two subdomains to applications running on different ports using reverse_proxy. We also added additional security headers (like HSTS) for api.example.com and specified a custom logging configuration for dashboard.example.com. Caddy will obtain Let’s Encrypt certificates for each domain name separately and manage them.
We can also define multiple domain names within the same block; in this case, Caddy will attempt to obtain a single certificate (a SAN certificate) for all these domain names:
# /etc/caddy/Caddyfile
www.example.com, example.com {
reverse_proxy localhost:5000
}
This is particularly useful for the www and non-www versions of a site. Caddy will bundle these two domain names under a single certificate. I’ve further simplified certificate management in my side projects using this approach. However, one must be careful; grouping too many domain names into a single certificate can cause you to hit Let’s Encrypt limits.
What are the Security and Performance Optimizations with Caddy?
Caddy’s automatic HTTPS management is already a significant security advantage in itself. However, Caddy doesn’t stop at just certificate management; it also provides various directives for additional security and performance optimizations. Using these features correctly helps our applications run both more securely and faster.
Security-Focused Optimizations:
-
HTTP/3 Support: Caddy supports the HTTP/3 (QUIC) protocol by default. This makes websites load faster by reducing latency and increasing connection efficiency. You don’t need to configure this feature specifically; Caddy uses it automatically.
-
Security Headers: As shown in the example above, we can add various security headers to HTTP responses using the
headerdirective. For instance,Strict-Transport-Security(HSTS) tells browsers to always connect to your site via HTTPS, while headers likeX-Frame-OptionsandX-Content-Type-Optionsprevent attacks like clickjacking and MIME type sniffing. -
Rate Limiting: To protect against DDoS attacks or malicious scans, we can use the
rate_limitdirective. This limits the number of requests from a specific IP address within a given time frame. For example, to limit requests to an API endpoint:api.example.com { reverse_proxy localhost:8080 rate_limit /api/v1/login 10r/m # 10 requests per minute to the login endpoint }This can be the first line of defense against brute-force attacks. I’ve used this method to control excessive requests to API endpoints in the backend of my Android spam blocker application.
-
Basic Auth: You might want to add simple username/password authentication for some internal services. Caddy’s
basicauthdirective easily fulfills this need:admin.example.com { basicauth { admin JDJhJDEwJEVMMlF... # Encrypted password } reverse_proxy localhost:9000 }
Performance-Focused Optimizations:
-
Compression: Caddy can reduce bandwidth usage and speed up loading times by compressing static and dynamic content using compression algorithms like
gziporzstd. Theencodedirective is used for this:example.com { root * /var/www/html/static_site file_server encode gzip zstd } -
Caching: With the
cachedirective, we can enable Caddy to cache specific responses. This reduces server load, especially for frequently accessed static files or API responses that change infrequently.api.example.com { reverse_proxy localhost:8080 cache { match_path /api/v1/products status_codes 200 max_age 5m } }
These optimizations demonstrate that Caddy can be used not just as an HTTPS proxy but as a fully-fledged and secure web server. However, it’s important to remember that every optimization has a trade-off; for example, caching can lead to issues with data freshness.
Caddy’s Limitations and Alternatives: When Should We Look at Other Solutions?
While Caddy is a great solution for most self-hosting scenarios and small to medium-sized applications, like any tool, it has its limitations and situations where it’s not the best fit. In very complex enterprise architectures or systems with high traffic and special requirements, it might be necessary to consider alternative solutions.
Situations Where Caddy Might Not Be Sufficient:
- Very Complex Routing and Load Balancing: If you need complex load balancing algorithms between multiple backend services (e.g., sticky sessions, more than just round-robin), advanced traffic management features (traffic shifting for A/B testing, blue-green deployments), Caddy’s capabilities might be limited. Kubernetes Ingress solutions (Nginx Ingress Controller, Traefik) or dedicated load balancers (HAProxy, Envoy) are usually more flexible in such scenarios.
- Advanced WAF (Web Application Firewall) and DDoS Protection: While Caddy offers basic protections like
rate_limit, it doesn’t replace specialized solutions like Cloudflare, Akamai, or ModSecurity for advanced Web Application Firewall (WAF) features, advanced DDoS attack detection and prevention capabilities, or bot management. Typically, such protections are positioned as a layer in front of Caddy. - Very Large-Scale Static File Serving: For systems serving billions of static files or reaching very high concurrent connection counts, Nginx, as a more established and optimized server, might offer better performance with its custom modules and fine-tuning options. While Caddy’s performance is good, Nginx’s experience and ecosystem in this area are still very strong.
- Need for Custom Modules: Nginx’s rich module ecosystem can offer very specific integrations or protocol support for particular needs. If Caddy’s core features or existing plugins don’t meet your requirements, Nginx’s modular structure might be a better option.
Alternatives and Their Trade-offs:
- Nginx: Industry standard, high performance, rich module ecosystem, proven stability. However, HTTPS management is not as automatic as Caddy’s, requires manual configuration, and needs additional tools like
certbotfor Let’s Encrypt integration. The learning curve is steeper for complex configurations. - Apache HTTP Server: Extensive feature set, flexibility with directory-based configuration via
.htaccess. Like Nginx, it requires additional tools for automatic HTTPS and can lag behind Nginx in performance in some scenarios. Setup and management are complex. - Traefik: An edge router designed for cloud-native environments and microservice architectures, with dynamic configuration capabilities. Like Caddy, it provides automatic HTTPS but is more integrated with container orchestration tools like Docker Swarm and Kubernetes. For a simple reverse proxy on a single server, it’s not as straightforward as Caddy.
graph TD;
A["User Access"] --> B{"Is Caddy Sufficient?"};
B -- "Yes (Simple Reverse Proxy, Static Site, Single Server)" --> C["Use Caddy"];
B -- "No (Complex Load Balancing, Advanced WAF, Large Scale)" --> D{"What is the Need?"};
D -- "High Performance, Module Flexibility" --> E["Nginx"];
D -- "Microservices, Dynamic Configuration" --> F["Traefik"];
D -- "Enterprise WAF/DDoS" --> G["Cloudflare / Akamai"];
In my practice, if the project is a simple website or an API backend, Caddy has always been my first choice. However, for a production ERP or a multi-layered e-commerce platform, there have been instances where I preferred Nginx due to its broader module support and greater fine-tuning capabilities. The choice always depends on the specific requirements of the project and its operational complexity.
Conclusion
Managing automatic HTTPS certificates and configuring a web server can sometimes be challenging, especially for those new to the self-hosting world or for developers who want to quickly set up a service. Caddy, by incredibly simplifying this process, is a powerful and modern tool that provides HTTPS automatically as soon as you define a domain name.
In this post, I’ve covered many topics, from Caddy’s installation to basic and advanced Caddyfile configurations, security and performance optimizations, and its limitations. In my own experience, the automation provided by Caddy has saved me from manual certificate renewal nightmares and allowed me to focus more on my projects. If you also want to publish services on your own server without worrying about HTTPS, Caddy is definitely a solution worth trying. In the next post, we will explore how you can manage your services more flexibly using Caddy’s dynamic configuration capabilities and its API.