In this tutorial, we'll learn how to set up Caddy Server on AlmaLinux 10.
What is Caddy?
Caddy is a modern, open-source web server written in Go. It’s designed to be simple, secure, and fully automated. What makes Caddy special is that it automatically manages HTTPS certificates using Let’s Encrypt — no manual setup or renewal needed. It can serve static websites, act as a reverse proxy for backend apps, and handle load balancing with clean, human-readable configuration. In short, Caddy turns complex server management into a one-file setup that just works.
Prerequisites
Before we begin, ensure we have the following:
- An AlmaLinux 10 dedicate server or KVM VPS.
- Basic Linux Command Line Knowledge.
- A domain name, pointing A record to server IP.
Set up Caddy Server on AlmaLinux 10
1) System update and minimal tools
sudo dnf update -y
sudo dnf install -y dnf-plugins-core policycoreutils-python-utils
2) Install Caddy (official COPR package for RHEL-family distros)
Caddy provides an official COPR repository for RHEL/Fedora families — easiest and recommended for production on AlmaLinux.
# Enable COPR and install
sudo dnf copr enable @caddy/caddy -y
sudo dnf install -y caddy
Confirm:
caddy version
# or
caddy -v
3) Configure Caddy
Open the main configuration file:
sudo nano /etc/caddy/Caddyfile
Replace its content with this simple setup:
example.com {
root * /var/www/example
file_server
}
Important: Replace example.com with our real domain that points to the server’s public IP address.
For proxy server
We proxy example.com to a backend running on 127.0.0.1:3000.
Edit /etc/caddy/Caddyfile:
nano /etc/caddy/Caddyfile
Add following content:
example.com {
encode zstd gzip
tls {
# leave blank — Caddy will manage ACME certificates automatically
}
# Reverse proxy to local app
reverse_proxy 127.0.0.1:3000 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
log {
output file /var/log/caddy/access.log {
roll_size 50mb
roll_keep 5
roll_keep_for 336h
}
level INFO
}
}
Notes: tls left default so Caddy uses ACME to provision certs automatically; encode enables compression; log file path and rotation are practical defaults.
Validate the Caddyfile:
sudo caddy validate --config /etc/caddy/Caddyfile
4) Create directories + permissions
Caddy runs as user caddy when installed from packages. Ensure directories and ownership are correct:
sudo mkdir -p /var/www/example.com
sudo chown -R caddy:caddy /var/www/example.com
sudo mkdir -p /var/log/caddy
sudo chown -R caddy:caddy /var/log/caddy
If we store site assets under /srv or another location, adapt ownership similarly.
5) Enable and start the Caddy systemd service
When installed via packages, Caddy ships systemd unit files. Start and enable:
sudo systemctl daemon-reload
sudo systemctl enable --now caddy.service
sudo systemctl status --no-pager --full caddy
If the service fails, inspect journal logs:
sudo journalctl -u caddy -e
Caddy will attempt ACME certificate issuance on first start for the configured hostnames.
6) Open firewalld ports (HTTP/HTTPS)
We must allow ports 80 and 443 in firewalld so ACME (HTTP challenge) and browser traffic work:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# verify
sudo firewall-cmd --list-all
If firewalld is not used, open those ports on whichever firewall/NAT appliance is in front of the server.
7) SELinux considerations (AlmaLinux default)
AlmaLinux ships SELinux enabled. Caddy may need limited SELinux allowances to:
- Allow outbound network connections for ACME challenges and to reach upstream backends.
- Ensure Caddy can write logs and access content directories under expected contexts.
Common fixes:
Permit outbound HTTP/S connections for webserver-type services:
# allow processes with httpd_t-like privileges to make network connections
sudo setsebool -P httpd_can_network_connect 1
Ensure file contexts for log and content directories are correct, then restore contexts:
# set proper types if needed, example for /var/www
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www(/.*)?"
sudo restorecon -Rv /var/www
# logs
sudo semanage fcontext -a -t var_log_t "/var/log/caddy(/.*)?"
sudo restorecon -Rv /var/log/caddy]
If SELinux denies are still seen, check sudo ausearch -m avc -ts recent or sudo journalctl -k and use audit2allow to craft precise rules.
If we cannot resolve a permission quickly in a non-production lab, temporarily set SELinux to permissive to diagnose (not recommended for long-term production):
sudo setenforce 0 # temporary (reverts on reboot)
Permanently edit /etc/selinux/config with SELINUX=permissive (if needed)
8) Verify HTTPS and functionality
Open a browser to https://example.com — we expect a lock icon and valid certificate.
From the server:
curl -I https://example.com
Check Caddy logs:
sudo tail -n 200 /var/log/caddy/access.log
sudo journalctl -u caddy -f
If Caddy fails to obtain a cert, typical root causes are:
- DNS not pointing at server IP (ACME validation fails).
- Port 80 blocked by cloud provider or NAT.
- SELinux blocking outbound requests or file access.
Multiple services already bound to ports 80/443 (nginx/apache). Caddy needs these ports for ACME HTTP challenge and HTTP->HTTPS redirect.
Security & production tips
- Run Caddy as the packaged caddy user (default) — do not run as root. Package takes care of that.
- Use a short health_path for backends so Caddy can detect failed backends and avoid routing to them.
- Keep backups of /etc/caddy/Caddyfile and any TLS-related environment variables for DNS plugins.
- For strict compliance environments, pin ACME issuer or use a private CA via tls directive.
Conclusion
We set up Caddy from the official repo, created a Caddyfile for reverse proxying apps and static content, confirmed automatic HTTPS behavior, covered wildcard/DNS challenge considerations, and included checks and production tips. Caddy removes a lot of the friction around TLS and reverse proxies; for most web apps it’s a lighter, simpler alternative to manual cert management and complex Nginx configs. Use the examples above as copy-paste templates and adapt headers/backends to our stack.

