What is Caddy?
Caddy is a powerful, enterprise-ready, open-source web server with automatic HTTPS written in Go. Unlike traditional web servers like Apache or Nginx, Caddy is designed with simplicity and security in mind, making it incredibly easy to deploy modern web applications.
Reference: https://caddyserver.com/
Why Choose Caddy?
- Automatic HTTPS
- Simple Setup
- HTTP/2 & HTTP/3
Automatic HTTPS
One of Caddy's standout features is automatic HTTPS. It automatically obtains and renews TLS certificates from Let's Encrypt without any configuration. This means your sites are secure by default!
HTTPS Certificate Flow:
Client ──➤ Caddy ──➤ Let's Encrypt ──➤ Certificate ──➤ Automatic Renewal
Zero Configuration
Caddy works out of the box with sensible defaults. You can serve a static website with just one command, no configuration files needed.
Simple Configuration
When you do need configuration, Caddy uses a simple, human-readable format called the Caddyfile. No complex syntax or cryptic directives.
# Apache Configuration (Complex)
- <VirtualHost *:80>
- ServerName caddy.localhost
- DocumentRoot /var/www/html
- <Directory /var/www/html>
- AllowOverride All
- Require all granted
- </Directory>
- </VirtualHost>
# Caddy Configuration (Simple)
+ caddy.localhost
+ root * /var/www/html
+ file_server
Modern Features
- HTTP/2 and HTTP/3 support
- Automatic compression
- Load balancing
- Reverse proxy capabilities
- API endpoints
- Plugin ecosystem
HTTP Evolution Timeline:
HTTP/1.1 (1997) ──➤ HTTP/2 (2015) ──➤ HTTP/3 (2022)
Single Multiplexing QUIC Protocol
Connection Streams UDP Based
Key Concepts
1. Caddyfile
The Caddyfile is Caddy's primary configuration format. It's designed to be easy to read and write:
caddy.localhost
root * /var/www
file_server
2. Directives
Directives tell Caddy what to do. Common directives include:
file_server: Serve static filesreverse_proxy: Proxy requests to backend serversrespond: Send custom responsesrewrite: Modify requests
3. Matchers
Matchers allow you to apply directives conditionally:
- Path matchers:
/api/* - Header matchers:
{header.user-agent}*curl* - Method matchers:
{method.POST}
Request Processing Flow:
Request → Matchers → Handlers → Response
Getting Started: Setting Up a Simple App
Let's build a simple web application setup with Caddy that serves both static files and a Node.js API.
Application Architecture:
Browser ↔ Caddy ↔ Node.js API
↓
Static Files
Project Structure
Let's create a simple project structure:
simple-app/
├── Caddyfile
├── docker-compose.yml
├── public/
│ ├── index.html
└── api/
├── package.json
└── server.js
1. create docker-compose.yml
services:
caddy:
image: caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
- ./public:/var/www/html
networks:
- caddy_net
api:
image: node:18
container_name: node_api
restart: unless-stopped
working_dir: /usr/src/app
volumes:
- ./api:/usr/src/app
command: sh -c "npm install && npm start"
ports:
- "3000:3000"
networks:
- caddy_net
volumes:
caddy_data:
caddy_config:
networks:
caddy_net:
driver: bridge
1. Create the Frontend
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Caddy App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1> Welcome to Caddy!</h1>
<p>This is a simple app served by Caddy web server.</p>
<div class="api-demo">
<h2>API Demo</h2>
<button id="fetch-data">Fetch Data from API</button>
<div id="result"></div>
</div>
<div class="features">
<h2>Caddy Features</h2>
<ul>
<li>Automatic HTTPS</li>
<li>HTTP/2 & HTTP/3</li>
<li>Zero Configuration</li>
<li>Hot Reload</li>
<li>Reverse Proxy</li>
</ul>
</div>
</div>
<script>
document.getElementById('fetch-data').addEventListener('click', async () => {
const resultDiv = document.getElementById('result');
resultDiv.textContent = 'Loading...';
try {
const response = await fetch('/api/data');
const data = await response.json();
resultDiv.textContent = `Message from API: ${data.message}`;
} catch (error) {
resultDiv.textContent = 'Error fetching data from API.';
}
});
</script>
</body>
</html>
2. Create the Backend API
api/package.json
{
"name": "simple-api",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5"
}
}
api/server.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
// Routes
app.get('/api/data', (req, res) => {
const data = {
message: 'Hello from Caddy-proxied API!',
timestamp: new Date().toISOString(),
server: 'Node.js + Express',
features: [
'Automatic HTTPS',
'Reverse Proxy',
'Load Balancing',
'Hot Reload'
],
stats: {
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.version
}
};
res.json(data);
});
app.get('/api/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString()
});
});
app.listen(PORT, () => {
console.log(`API server running on http://localhost:${PORT}`);
console.log(`Health check: http://localhost:${PORT}/api/health`);
});
3. Configure Caddy
Caddyfile
caddy.localhost {
# Reverse Proxy to Node.js API
reverse_proxy /api/* api:3000
# Serve static files for everything else
root * /var/www/html
file_server
handle /health {
respond "OK" 200
}
log {
output file /var/log/caddy/access.log
format json
}
}
4. Running the Application
docker compose up -d