Installing on CentOS / RHEL #
Red Hat Enterprise Linux (RHEL)-based operating systems and their derivatives — such as Rocky Linux, AlmaLinux, CentOS Stream, and Fedora — have high-security standards that differ significantly from the Debian or Ubuntu family. The main difference lies in SELinux (Security-Enhanced Linux) and firewalld, both active by default on these systems. These two components often block Caddy’s network and file access if you don’t configure them properly.
This article covers the Caddy installation process on the RPM-based ecosystem comprehensively. We won’t just cover the basic install commands; we’ll also focus on how to configure SELinux safely (without disabling SELinux) and set up firewalld rules for your production environment.
Supported Distributions #
This guide is designed for full compatibility with the following operating systems:
Distribution Supported Versions
─────────────────────────────────────────────────
Fedora 39, 40, 41+
CentOS Stream 8, 9
RHEL 8, 9
Rocky Linux 8, 9
AlmaLinux 8, 9
Oracle Linux 8, 9
[!WARNING] CentOS 7 reached End-of-Life (EOL) status in June 2024. Using CentOS 7 in production is strongly discouraged due to the lack of security updates. If you’re still on CentOS 7, migrating to Rocky Linux 9 or AlmaLinux 9 first is strongly recommended before proceeding with a Caddy deployment.
Installing via the Official COPR Repository #
In the RPM ecosystem, the Caddy developer team provides official packages through COPR (Cool Other Package Repo), managed directly under the Fedora Copr umbrella. This is the most stable installation method and is fully integrated with the DNF or YUM package manager.
1. Installing on Fedora #
Fedora ships the COPR plugin by default, so you can enable the Caddy repository right away:
# Enable the official Caddy repository on Fedora
sudo dnf copr enable -y @caddy/caddy
# Install the Caddy Web Server
sudo dnf install -y caddy
2. Installing on RHEL, Rocky Linux, AlmaLinux, and CentOS Stream (Versions 8 and 9) #
On RHEL Enterprise derivatives, you need to make sure the COPR command plugin for DNF is installed before enabling the repository:
# Step 1: Make sure the copr command plugin is installed
sudo dnf install -y 'dnf-command(copr)'
# Step 2: Enable the Caddy repository
sudo dnf copr enable -y @caddy/caddy
# Step 3: Update package metadata and install Caddy
sudo dnf install -y caddy
After the installation process finishes, you can validate the Caddy installation:
# Check the installed Caddy version
caddy version
# Check the default systemd service status
sudo systemctl status caddy
Understanding and Configuring SELinux #
SELinux is a Mandatory Access Control security system that analyzes every process at the kernel level. On RHEL and Rocky Linux, SELinux is in Enforcing mode (actively blocking suspicious activity) by default.
[!CAUTION] Never Disable SELinux! Many guides on the internet suggest setting SELinux to
DisabledorPermissivemode when Caddy hits a Permission Denied error. This is a bad solution that endangers your server’s security. If your server gets breached, SELinux is the last line of defense preventing attackers from reaching the rest of the system. The right solution is to adjust the SELinux policy to legally allow Caddy’s activity.
1. Check the Current SELinux Mode #
You can verify SELinux status on your server with the following commands:
# Check the active SELinux status
getenforce
# Expected output: Enforcing
# Show detailed SELinux configuration status
sestatus
2. Set SELinux Booleans for Network Access #
By default, SELinux blocks web server processes (like Caddy or Nginx running under the httpd_t context) from initiating outbound connections or acting as a relay. Caddy desperately needs this permission because it acts as a reverse proxy (contacting backend apps) and performs ACME TLS negotiation with Let’s Encrypt.
You can allow this activity using SELinux Boolean switches:
# Allow Caddy (httpd-typed process) to initiate outbound connections (ACME challenge)
sudo setsebool -P httpd_can_network_connect 1
# Allow Caddy to relay network connections (Reverse Proxy to backend apps)
sudo setsebool -P httpd_can_network_relay 1
# Verify that both Booleans above are now set to 'on'
getsebool httpd_can_network_connect httpd_can_network_relay
# Expected output:
# httpd_can_network_connect --> on
# httpd_can_network_relay --> on
The -P flag ensures these changes are saved permanently (persistent) into the system policy and won’t be lost when your server reboots.
3. Configuring File Contexts for Custom Webroots #
SELinux applies a security type label to every file on disk. Caddy can only read files labeled with the httpd_sys_content_t type (static web files) or httpd_sys_rw_content_t (if it needs write access).
If you put your website files outside the default /var/www/html directory — for example, in /home/you/yoursite or /data/www — Caddy will produce a 403 Forbidden error because those files have the wrong security type label.
Here’s how to register SELinux context labels permanently:
# Step 1: Add a permanent file context rule for your custom directory
# The "(/.*)?" pattern ensures every file inside that folder gets the same label
sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
# Step 2: Apply those label changes to the physical files on disk (restore context)
sudo restorecon -Rv /data/www
# Step 3: Verify the new context labels on your files
ls -lZ /data/www
# Output must include the httpd_sys_content_t type context marker
4. Diagnosing and Resolving AVC Denials #
If Caddy still has access problems, you can check the SELinux audit log for denial messages (AVC denial):
# Show the latest SELinux denials related to Caddy
sudo ausearch -m avc -ts recent
# Translate complex audit messages into human-readable explanations
sudo ausearch -m avc -ts recent | audit2why
If you find Caddy needs a special permission that can’t be solved via standard Booleans, you can package those denial messages into a custom policy module:
# Create a policy module named caddy-custom from the latest denial log
sudo ausearch -m avc -ts recent | audit2allow -M caddy-custom
# Install that policy module into the system kernel
sudo semodule -i caddy-custom.pp
Configuring Firewalld #
Firewalld is the built-in firewall manager on CentOS, RHEL, and Rocky Linux. It uses the concept of zones to determine connection trust levels. By default, inbound traffic to ports 80 and 443 is completely blocked on the public zone.
You must open these ports permanently so internet users can reach Caddy.
1. Check the Active Zone #
# Check the default zone used by your network interface
sudo firewall-cmd --get-active-zones
# Example output: public
2. Open HTTP and HTTPS Ports #
You can register web services into the firewalld rules permanently:
# Open access to port 80 (HTTP) permanently
sudo firewall-cmd --permanent --zone=public --add-service=http
# Open access to port 443 (HTTPS TCP) permanently
sudo firewall-cmd --permanent --zone=public --add-service=https
# Open access to port 443 (HTTPS UDP) for the HTTP/3 protocol (QUIC)
sudo firewall-cmd --permanent --zone=public --add-port=443/udp
# IMPORTANT: Reload firewalld so the permanent rules above become active at runtime
sudo firewall-cmd --reload
# Verify the currently open services and ports
sudo firewall-cmd --zone=public --list-all
# Output must include http, https under 'services' and 443/udp under 'ports'
Managing Caddy with Systemd #
In the RHEL ecosystem, the systemd service unit is installed at /usr/lib/systemd/system/caddy.service. You can control the Caddy process using the standard systemctl utilities:
# Start the Caddy service and enable it at boot
sudo systemctl enable --now caddy
# Check Caddy's detailed status
sudo systemctl status caddy
# Track Caddy runtime logs to monitor ACME transaction activity
sudo journalctl -u caddy -f
To apply configuration changes in the Caddyfile, always use the reload command to minimize downtime:
# Validate, and if it passes, reload gracefully
caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy
Directory Structure on CentOS / RHEL #
Here’s a summary of important Caddy file locations after installation on CentOS/RHEL operating systems:
| File / Directory Name | Path | Description |
|---|---|---|
| Executable Binary | /usr/bin/caddy | The Caddy program file that runs. |
| Configuration File | /etc/caddy/Caddyfile | Your web server’s main configuration. |
| Certificate Storage | /var/lib/caddy/.local/share/caddy | SSL key and ACME storage directory. |
| Service Unit | /usr/lib/systemd/system/caddy.service | The systemd service lifecycle configuration. |
Initial Configuration and Caddyfile Validation #
Let’s replace the default Caddyfile configuration with a reverse proxy scenario commonly used in production:
# Open the main configuration file
sudo nano /etc/caddy/Caddyfile
Write your web server configuration like the example below:
# Global Configuration
{
email [email protected]
}
# Main Domain Block
yoursite.com {
# Forward traffic to the backend app on port 8080
reverse_proxy localhost:8080
}
# Documentation Subdomain Block
docs.yoursite.com {
root * /data/www/docs
file_server
encode gzip zstd
}
Before reloading these changes, validate the syntax:
# Run the Caddyfile structure validation
caddy validate --config /etc/caddy/Caddyfile
If validation succeeds, reload:
sudo systemctl reload caddy
Troubleshooting Common Cases on RHEL/CentOS #
1. Reverse Proxy Returns HTTP 502 Bad Gateway #
If you get a 502 message in your browser, and the Caddy journalctl log shows the error:
dial tcp 127.0.0.1:8080: connect: permission denied
This is a classic sign that SELinux is blocking the connection from Caddy to your backend app port. Solution:
# Make sure the SELinux Booleans for outbound network connections are on
sudo setsebool -P httpd_can_network_connect 1
sudo setsebool -P httpd_can_network_relay 1
2. Static Files Return HTTP 403 Forbidden #
If your site’s HTML files are in /var/www/html or a custom directory, yet Caddy responds 403 even though the UNIX file permissions (chmod) are already correct (755).
Solution:
The cause is that the SELinux file context on your directory isn’t recognized by the httpd_t type.
# Fix the SELinux context recursively
sudo semanage fcontext -a -t httpd_sys_content_t "/path/to/webroot(/.*)?"
sudo restorecon -Rv /path/to/webroot
3. Web Server Runs but Can’t Be Reached from Outside #
If you run curl -I localhost:80 from inside the server and get a successful response, but connections from outside the server hit a Connection Timeout.
Solution:
Firewalld hasn’t been configured to allow inbound HTTP/HTTPS traffic on your network interface’s zone.
# Open the service access in firewalld
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload
CentOS/RHEL Deployment Checklist #
Use the checklist table below to make sure every configuration aspect has been met before your server goes public:
[ ] REPOSITORY : DNF packages installed officially from COPR @caddy/caddy.
[ ] SELINUX : Status is Enforcing (not disabled).
[ ] BOOLEAN : httpd_can_network_connect is set to 1 (on).
[ ] BOOLEAN : httpd_can_network_relay is set to 1 (on).
[ ] CONTEXT : Web document directory has the httpd_sys_content_t context type.
[ ] FIREWALL : firewalld opens the http, https services and port 443/udp permanently.
[ ] DNS : Domain points to the server's external public IP address.
[ ] VALIDATION : caddy validate returns "Valid configuration".
[ ] SYSTEMD : The caddy.service is "active (running)" and "enabled".
Summary #
- COPR Repository — The official installation path for Caddy RPM packages on RHEL, Rocky Linux, and AlmaLinux distributions.
- SELinux Booleans — You must enable the
httpd_can_network_connectandhttpd_can_network_relayBooleans so Caddy can act as a reverse proxy and request SSL certificates.- SELinux Context — Always set your static web document file context to the
httpd_sys_content_ttype usingsemanage fcontextandrestorecon.- Firewalld Ports — Allow the HTTP and HTTPS services in firewalld plus port 443/udp to enable maximum HTTP/3 performance.
- Graceful Management — Apply Caddyfile configuration changes using
systemctl reload caddyto avoid dropping client connections.