How do I set up a systemd service file for Caddy that safely passes DNS API tokens

New Chat
/
auto-free-cloud
/
Select a Prompt
How do I set up a systemd service file for Caddy that safely passes DNS API tokens?
auto-free-cloud
Thought for 13s
To set up a systemd service for Caddy that safely handles DNS API tokens (like for Let's Encrypt), you must avoid passing the token as a command-line argument. Command-line arguments are easily visible via ps aux or shell history.
Instead, you should use Environment Files (.env) to securely inject the token into the process environment.
Here is the step-by-step guide to doing this safely.

1. Prerequisites

Ensure you have Caddy installed and a caddy user. The official Caddy packages usually handle this automatically.

  • User: The service will typically run as caddy.
  • Group:caddy.

2. Create a Secure Directory for Configuration

We will store the environment file and Caddy configuration in a dedicated, secure directory.
bash
sudo mkdir -p /etc/caddy

3. Create the Environment File (The Token Store)

This is the most critical step for security. Never put the token in the systemd unit file or the Caddy config file directly.
Create a file named /etc/caddy/caddy.env. You can use your editor of choice (e.g., nano, vim).
bash
sudo nano /etc/caddy/caddy.env
Replace the placeholder with your actual DNS API token:
text

Replace "YOUR_DNS_PROVIDER_API_TOKEN" with your actual token

 CLOUDFLARE_API_TOKEN=abcdef0123456789ghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
Set strict permissions: This file should be readable by the caddy user but not writable (to prevent the user from modifying it accidentally), and not readable by the general public.
bash

Set owner to root:caddy and permissions to 640 (Owner:rw, Group:r, Others: none)

 sudo chown root:caddy /etc/caddy/caddy.env
sudo chmod 640 /etc/caddy/caddy.env

4. Create the Caddy Configuration File

Create your Caddyfile at /etc/caddy/Caddyfile. Here is a standard example that uses the token for DNS validation (ACME).
bash
sudo nano /etc/caddy/Caddyfile
Example Caddyfile:
text
{
# Use the environment variable for the DNS API token
# Syntax: dns {ENV_VAR_NAME}      dns cloudflare {CLOUDFLARE_API_TOKEN}

    
# Optional: email for Let's Encrypt
admin your-email@example.com
}

 
example.com, www.example.com {
root /var/www/caddy
file_server
}
Note: Replace * cloudflare and {$CLOUDFLARE_API_TOKEN} with the provider you are using (e.g., digitalocean , route53 ). The curly braces {} tell Caddy to look for the environment variable.

5. Create the Systemd Service File

Create the service file. Systemd allows us to EnvironmentFile= which reads the .env file into the service's environment securely.
bash
sudo nano /etc/systemd/system/caddy.service
Paste the following content:
ini
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Wants=network-online.target

 
[Service]
Type=notify
User=caddy
Group=caddy
# Point to the secure environment file
EnvironmentFile=/etc/caddy/caddy.env
# Path to the Caddyfile
ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
Restart=on-failure
RestartSec=5
TimeoutStopSec=5
# KillMode=mixed preserves caddy processes on restart
KillMode=mixed

 
[Install]
WantedBy=multi-user.target

6. Verify and Reload

Before starting the service, it is good practice to check if the user and file permissions are correct.

  1. Reload systemd to pick up the new service:
  2. bash
  3. sudo systemctl daemon-reload
  4. Check the syntax of the configuration (Dry run):
  5. bash
  6. sudo -u caddy caddy validate --config /etc/caddy/Caddyfile

7. Start and Enable the Service

Start the service and enable it to run on boot.
bash
sudo systemctl start caddy
sudo systemctl enable caddy

8. Check Status

Check the status to ensure it loaded the environment variables and the DNS token is available in the process context.
bash
sudo journalctl -u caddy -f

Why This is "Safe"

  1. Not in Command Line: The token is stored in /etc/caddy/caddy.env. It is never passed as an argument to the ExecStart command. This prevents ps aux from displaying the token.
  2. File Permissions:chmod 640 ensures only root and the caddy user can read the file. If an attacker compromises the caddy user (unlikely as Caddy runs as a low-privilege user), they cannot read the file because it isn't world-readable.
  3. Systemd Isolation: Systemd manages the environment. The token is injected directly into the process memory, not written to disk by the service itself.

Troubleshooting Tips

  • Token Not Working: Ensure CLOUDFLARE_API_TOKEN matches the variable name used in your Caddyfile ({$CLOUDFLARE_API_TOKEN}).
  • Permission Denied: If Caddy cannot read the env file, check chmod and chown. Caddy needs read access (r), so 640 works (owner root, group caddy).
  • Systemd Ignoring EnvFile: Ensure there is no space after EnvironmentFile=/etc/caddy/caddy.env and that the path is absolute.
    Will caddy be on port 80 and 443
    auto-free-cloud
    Thought for 1s
    Yes. By default, Caddy is designed to serve both HTTP (port 80) and HTTPS (port 443). When you run the systemd service we just set up, Caddy will attempt to bind to:
  • Port 80 – For plain HTTP traffic (used for initial client discovery and redirecting to HTTPS).
  • Port 443 – For encrypted HTTPS traffic (handled via Let's Encrypt or other ACME providers).

What Happens If Ports Are Already In Use?

If something else is already listening on port 80 or 443 (for example, another web server like Nginx or Apache), Caddy will fail to start with an error like:
text
Failed to bind to 0.0.0.0:80: Address already in use
Solution: Before starting the service, verify the ports are free:
bash
sudo ss -tlnp | grep ':80|:443'
If you see output indicating a conflict, stop the conflicting service or change Caddy's listening ports in its configuration.

Managing Certificates (Since You're Using DNS Tokens)

Because you are using a DNS API token (for Let's Encrypt), Caddy will automatically request and renew certificates. The systemd service handles this seamlessly—no extra steps are required beyond having a valid Caddyfile pointing to your DNS provider.
However, make sure your system has a way to receive the renewal emails (if using SMTP) or ensure the DNS provider supports automatic renewal. Most modern DNS providers (Cloudflare, Route53, etc.) handle this automatically.

Quick Summary

Table
SettingDefault BehaviorHTTP ListenerPort 80 (redirects to HTTPS) HTTPS ListenerPort 443 (uses DNS token for ACME) ConfigurationHandled inside /etc/caddy/Caddyfile
Your current systemd setup is correct for this behavior. Just ensure the ports aren't occupied before starting.
Then I will have to shut down my Apache PHP server? Does Caddy do PHP?
auto-free-cloud
Thought for 4s
Short answer:

  • Yes, you cannot run ***both* Apache (on port 80/443) *and* Caddy (attempting to use ports 80/443) simultaneously without one stopping or one listening on a different port.**
  • Yes, Caddy ***does support PHP* natively** (via its built-in PHP plugin—no extra Apache modules needed).
    Here is a clear, practical, step-by-step explanation (no jargon overload):

1. Why you'd need to stop Apache ***if you want Caddy on ports 80/443***

  • Apache typically listens on port 80 (HTTP) and port 443 (HTTPS) by default when configured for HTTPS.
  • Caddy also tries to listen on port 80 (for HTTP → HTTPS redirect) and port 443 (for HTTPS) by default.
  • You cannot have two web servers bound to the same port (e.g., both Apache and Caddy trying to use port 80) on the same machine. Linux will reject the second bind (Address already in use).
  • If you want Caddy to serve your site ***on port 80/443* (the standard public ports, required for Let's Encrypt/DNS tokens), you must stop Apache *running on those ports*.
    Practical choice:**
    For production: Stop Apache (temporarily or permanently) and run Caddy on ports 80/443.
    For testing/migration: Run Caddy on a different port (e.g., 8080), keep Apache on 80/443 temporarily, verify Caddy works, then switch.

2. How to run Caddy with PHP (no extra Apache setup needed)

Caddy includes a built-in PHP handler (no separate php-fpm or Apache .htaccess tricks required for basic PHP). You just need:

  1. Install the PHP interpreter (if not already installed). On most Debian/Ubuntu/RHEL systems:
  2. bash
  3. Ubuntu/Debian

     sudo apt install php php-cli php-fpm

 
# RHEL/CentOS/Fedora
sudo dnf install php php-cli php-fpm
4. (Caddy uses php-fpm internally; installing php-fpm is sufficient. php-cli usually comes with it.)
5. Enable PHP in your Caddyfile using the @php directive (or php directive). Example Caddyfile:
6. caddy
7. {
# (Your existing dns line, e.g., dns cloudflare {$CLOUDFLARE_API_TOKEN})
}

 
example.com {
php /path/to/your/php/files  # <-- This line enables PHP for this site
root /var/www/caddy      # <-- Your PHP files go here (e.g., /var/www/caddy/index.php)
file_server                  # Serves static files (HTML, CSS, JS)
}
8.
How it works:*php /path/to/your/php/files tells Caddy: "For requests to example.com, if the request is for a .php file under /path/to/your/php/files, run it with PHP; otherwise serve static files." No .htaccess, no Apache phpadmin config needed.
Verification:
Place a simple index.php in /var/www/caddy (e.g., ). Reload the site in a browser → you see "Hello from Caddy PHP!" (works over HTTPS if using Let's Encrypt).

3. Step-by-step practical options (choose one)

Table
GoalActionWhy?"I want Caddy on the public ports (80/443) for Let's Encrypt" (recommended for production)1. Stop Apache: sudo systemctl stop apache2 (or httpd, check your service name)
2. Start Caddy: sudo systemctl start caddyCaddy binds to 80/443; Apache would conflict. Stopping Apache removes the conflict. Caddy handles PHP via php /path/... in Caddyfile. "I want to test Caddy without touching Apache yet"1. Change Caddy to listen on port 8080 temporarily in Caddyfile:
example.com { php /var/www/caddy; root /var/www/caddy; listen 127.0.0.1:8080 }
2. Start Caddy (sudo systemctl start caddy)
3. Visit http://localhost:8080 in browser to test PHPNo conflict with Apache (Apache still on 80/443). Lets you verify Caddy + PHP works before releasing to production ports. "I want to keep Apache running for now"1. Stop Apache: sudo systemctl stop apache2 (or equivalent)
2. Start Caddy: sudo systemctl start caddy
3.
(Later) Stop Caddy if you want to revert, or change Caddy to port 8080 for coexistenceSimplest safe production path: one web server owns port 80/443. You replace Apache with Caddy.
🔍 How to check which service is using port 80/443 (quick command):
bash
sudo ss -tlnp | grep ':80|:443'
Example output:
text
LISTEN 0      128    0.0.0.0:80      0.0.0.0:
    users:(("apache2",pid=1234,fd=3))
LISTEN 0      128    0.0.0.0:443     0.0.0.0:*    users:(("apache2",pid=1234,fd=4))
→ Shows Apache is listening. If you see Caddy (caddy, user=caddy) instead, Apache is stopped.

Summary in plain language

"Yes, if you want Caddy to use the standard public ports (80 for HTTP, 443 for HTTPS – which you need for Let's Encrypt and your DNS API token), you must stop your current Apache server (because both Apache and Caddy cannot listen on the same ports at the same time).
However, Caddy supports PHP natively. You just need to:

  1. Install php-fpm (usually via apt install php-fpm or dnf install php-fpm),
  2. Add php /var/www/caddy (or your PHP file path) to your Caddyfile under the site block, and
  3. Restart Caddy.
    For testing, you can run Caddy on port 8080 (listen 127.0.0.1:8080) alongside Apache temporarily to verify PHP works, then switch to 80/443 for production by stopping Apache."
    Bottom line:
  • Production: Stop Apache → start Caddy (on 80/443). Caddy handles PHP via php /path/in/caddyfile in the Caddyfile.
  • Testing: Run Caddy on port 8080 (no Apache stop needed), verify PHP works, then switch to 80/443 for production.
    (No need to install Apache PHP modules or configure php-fpm separately beyond installing php-fpm ; Caddy manages the PHP-fpm connection internally.)
    Apache serves from /var/www/html. Can Caddy serve from /var/www/html at the same time but using a different port (8080).
    auto-free-cloud
    Thought for 17s
    Yes, absolutely! You can run Caddy on port 8080 while Apache continues serving from /var/www/html on port 80. They can coexist perfectly for testing or gradual migration.
    Here's the complete setup:

1. Keep Apache Running (Port 80)

Apache will continue serving normally:

  • Port: 80 (HTTP) and 443 (HTTPS if configured)
  • Document Root:/var/www/html

2. Configure Caddy for Port 8080

Modify your Caddyfile to listen on port 8080 and serve from /var/www/html:
bash
sudo nano /etc/caddy/Caddyfile
Updated Caddyfile:
text
{
# Use the environment variable for the DNS API token
dns cloudflare {$CLOUDFLARE_API_TOKEN}

    
# Optional: email for Let's Encrypt
admin your-email@example.com
}

 
# Test configuration - serve on port 8080
:8080 {
root * /var/www/html
php
file_server
}

 
# Production configuration (commented out for now)
# example.com, www.example.com {
#     root * /var/www/html
#     php
#     file_server
# }

3. Update Systemd Service for Port 8080

Modify the systemd service to use the test configuration:
bash
sudo nano /etc/systemd/system/caddy.service
Updated service file:
ini
[Unit]
Description=Caddy (Test on port 8080)
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Wants=network-online.target

 
[Service]
Type=notify
User=caddy
Group=caddy
EnvironmentFile=/etc/caddy/caddy.env
# Use the test configuration with port 8080
ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
Restart=on-failure
RestartSec=5
TimeoutStopSec=5
KillMode=mixed

 
[Install]
WantedBy=multi-user.target

4. Start Caddy on Port 8080

bash
sudo systemctl daemon-reload
sudo systemctl start caddy
sudo systemctl enable caddy

5. Verify Both Services Are Running

Check that both Apache and Caddy are listening on their respective ports:
bash

Check what's listening on ports 80 and 8080

 sudo ss -tlnp | grep ':80|:8080'

 
# You should see something like:
# LISTEN 0      128    0.0.0.0:80      0.0.0.0:    users:(("apache2",pid=1234,fd=3))
# LISTEN 0      128    0.0.0.0:8080     0.0.0.0:
    users:(("caddy",pid=5678,fd=4))

6. Test Both Services

  • Apache (port 80):http://your-server-ip (should show your existing site)
  • Caddy (port 8080):http://your-server-ip:8080 (should show the same files, but served by Caddy)

7. Test PHP in Caddy

Create a test PHP file to verify PHP processing:
bash
sudo nano /var/www/html/test-caddy.php
php

";  echo "PHP Version: " . phpversion() . "
";  echo "Document Root: " . $_SERVER['DOCUMENT_ROOT'] . "
";  echo "Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "
";  ?>

Visit: http://your-server-ip:8080/test-caddy.php

8. When Ready to Switch to Production

When you're satisfied that Caddy works correctly with your PHP application:

  1. Stop Apache:
  2. bash
  3. sudo systemctl stop apache2
  4. Update Caddyfile for production:
  5. text
  6. {
    dns cloudflare {$CLOUDFLARE_API_TOKEN}
    admin your-email@example.com
    }

 
example.com, www.example.com {
root * /var/www/html
php
file_server
}
7. Restart Caddy:
8. bash
9. sudo systemctl restart caddy

Key Benefits of This Approach:

  • Zero downtime migration: Test Caddy alongside Apache
  • Same files: Both servers use /var/www/html
  • Easy rollback: Just stop Caddy and restart Apache if issues
  • Safe testing: Verify PHP, SSL, and all functionality before going live
    This gives you a perfect testing environment to ensure Caddy handles your PHP application correctly before switching your production traffic!
    Can Caddy be a proxy server at the same time?
    auto-free-cloud
    Thought for 16s
    Yes, absolutely! Caddy is exceptionally well-suited as a reverse proxy server*while simultaneously serving your own static/PHP site* (or acting as an origin server for other services). This is one of its core strengths — and it's trivially simple to configure compared to Apache/Nginx.
    Here's exactly how to do it, building on your current setup (Apache on 80/443, Caddy testing on 8080 serving /var/www/html):

✅ Key Concept: Caddy Can Do ***Both* at Once**

In a single Caddyfile, you can:

  • Serve your own site (e.g., example.com → /var/www/html)
  • Proxy to backend services (e.g., example.com/api → localhost:3000, example.com/grafana → localhost:3001)
  • All under the same domain, with automatic HTTPS (once you switch to ports 80/443)

🛠️ Step-by-Step: Add Reverse Proxying to Your Test Setup (Port 8080)

1. Edit your Caddyfile (/etc/caddy/Caddyfile):
Keep your existing site configuration, then add reverse_proxy directives where needed.
text
{
dns cloudflare {$CLOUDFLARE_API_TOKEN}
admin your-email@example.com
}

 
# Your main site (serving /var/www/html) - UNCHANGED
:8080 {
root * /var/www/html
php
file_server
}

 
# Example 1: Proxy a Node.js app running on localhost:3000
# Access via: http://your-server:8080/node-app
:8080/node-app {
reverse_proxy localhost:3000
}

 
# Example 2: Proxy Grafana running on localhost:3001 under /grafana
# Access via: http://your-server:8080/grafana
:8080/grafana {
reverse_proxy localhost:3001
}

 
# Example 3: Proxy multiple services under different subpaths (all on :8080)
:8080 {
# ... your existing site config (root/php/file_server) ...

 
# Proxy /api to a backend API
reverse_proxy /api* localhost:4000

 
# Proxy /admin to a different backend
reverse_proxy /admin* localhost:5000
}
💡 How it works:

  • The reverse_proxy directive tells Caddy: "For requests matching this path, forward them to this backend server."
  • Caddy handles TLS termination (later on 80/443), load balancing, health checks, WebSockets, and more — all automatically.
  • The backend (localhost:3000, etc.) only needs to speak plain HTTP (no TLS needed locally).

🔧 Step-by-Step: Test It

  1. Start a test backend (e.g., a simple Node.js app on port 3000):
  2. bash
  3. Install http-server for testing (if you don't have a real backend)

     sudo npm install -g http-server

 
# Create a test directory and start it on port 3000
mkdir -p /tmp/node-app && echo "Hello from Node.js!" > /tmp/node-app/index.html
http-server /tmp/node-app -p 3000 &
4. Reload Caddy:
5. bash
6. sudo systemctl reload caddy
7. Test in your browser:

  • Main site: http://your-server-ip:8080 → Shows /var/www/html (your PHP site)
  • Node.js proxy: http://your-server-ip:8080/node-app → Shows "Hello from Node.js!"
  • Grafana proxy (if running): http://your-server-ip:8080/grafana → Shows Grafana login
  1. Verify in logs (optional):
  2. bash
  3. sudo journalctl -u caddy -f
  4. You’ll see lines like:
    proxy: upstream roundtrip: completed request to "http://localhost:3000/" in 1.2ms

⚙️ Critical Notes for Your Use Case

Table
ScenarioHow to HandleStill testing on port 8080?Use :8080 in the Caddyfile (as shown above). Backends can be localhost:PORT. Ready for production (ports 80/443)?Change :8080 → example.com, www.example.com (or just :80 for HTTP, then Caddy auto-upgrades to HTTPS via ACME). Backend is on another machine?Replace localhost:3000 → backend.internal:3000 or 10.0.0.5:3000. Need WebSockets (e.g., Socket.io)?Caddy handles this automatically — no extra config needed! Just reverse_proxy. Want load balancing?reverse_proxy /api* { to localhost:4000 localhost:4001 } (Caddy does round-robin by default). Backend needs specific headers?Use header_up or header_down inside reverse_proxy (e.g., header_up Host {host}).

🔄 When You Switch to Production (Ports 80/443)

Once you stop Apache and move Caddy to ports 80/443:

  1. Update your Caddyfile to use your domain (not :8080):
  2. text
  3. {
    dns cloudflare {$CLOUDFLARE_API_TOKEN}
    admin your-email@example.com
    }

 
example.com, www.example.com {
root * /var/www/html
php
file_server

 
# Proxy examples (now on port 443 with auto-HTTPS!)
reverse_proxy /api localhost:4000
reverse_proxy /grafana
localhost:3001
}
4. Stop Apache: sudo systemctl stop apache2
5. Restart Caddy: sudo systemctl restart caddy
6. Visit https://example.com → Your site
Visit https://example.com/api → Proxied to your backend (with valid Let's Encrypt cert!)

💡 Why This Is Better Than Apache/Nginx

  • Apache: Requires ProxyPass, ProxyPassReverse, mod_proxy, mod_proxy_http, complex blocks, and often .htaccess fiddling.
  • Nginx: Requires proxy_pass, proxy_set_header, proxy_http_version, and careful location ordering.
  • Caddy: One line: reverse_proxy /path* backend:port.
    → Automatic HTTPS, HTTP/2, WebSockets, graceful reloads, zero-config TLS renewals.
    No module installation. No recompiling. No confusing directives.

✅ Final Verification

After adding proxy rules:

  1. Confirm backends are running (e.g., curl localhost:3000 returns expected output).
  2. Reload Caddy: sudo systemctl reload caddy.
  3. Test proxied paths: curl -I http://localhost:8080/node-app → Should show 200 OK and headers from your backend.
  4. Check Caddy logs for proxy: upstream roundtrip entries.
    You now have a single Caddy instance serving your PHP site ***and* acting as a production-grade reverse proxy — all in a clean, secure, maintainable Caddyfile. This is how modern infrastructure should work. 🚀
    OnOff
    Submit
    Chat History
    Projects
    New project
    Your chats**
    Today
    How do I set up a systemd service file for Caddy that safely passes DNS API tokens?
    Last 7 Days
    why is the sky blue
    why is the sky blue
    why is the sky blue
    why is the sky blue
    who is samson
    why is the sky blue
    why is the sky blue
    why is the sky blue
    Copy to clipboard
    Read aloud
    Copy to clipboard
    Copy to clipboard