Caddy: A Fresh and Free Web Server Subordinate to the author Server-side Application Development and System Architecture Nginx has been used in our company before, but its configuration includes some feature support which is slightly more complicated than Caddy, which can be referred to by the author. Nginx Basic Configuration Memorandum.
Caddy: A Fresh and Free Web Server
As a new Web server, Caddy provides many simple and easy-to-use functions without the burden of history. Its default support and can help you automatically configure HTTP/2, HTTPS, IPV6, WebSockets have good support. Caddy, written on Go, naturally supports multiple cores, and its rich plug-in system provides out-of-the-box extensions for file management, file upload, MarkDown-based blog system, and so on.
We can be at Official Download Interface Select the plug-in function you need to customize the personalized binary file, after downloading, you can use the caddy command to run directly. Its default listens on port 2015 and opens in browser http://localhost:2015 That is to say, you can see how it works. We can also specify configuration files by using the - conf parameter:
$ caddy -conf="/path/to/Caddyfile"
We will introduce the configuration syntax of Caddyfile in detail below. One of the characteristics of Caddy is that it uses so-called Directives to describe functions for configuration. Compared with Nginx or Apache, its configuration is much simpler. If we want to support multiple configuration files, we can use the import directive:
import config/common.conf
Or introduce the entire folder:
import ../vhosts/*
Reference
Site Configuration
The typical Caddyfile configuration file is as follows:
localhost gzip browse websocket /echo cat ext .html log /var/log/access.log proxy /api 127.0.0.1:7005 header /api Access-Control-Allow-Origin *
The first line of each Caddyfile must describe the address of its service:
localhost:2020
Each subsequent line is officially provided with instructions. For example, we need to add gzip compression support to the server. We just need to add one instruction directly:
localhost:2020 gzip
We can use the bind instruction to specify the address of the current server binding:
bind host bind 127.0.0.1
Virtual Host
If we need to configure a separate virtual host, we need to move the configuration information to brackets after the site name:
mysite.com { root /www/mysite.com } sub.mysite.com { root /www/sub.mysite.com gzip log ../access.log }
Note that the left parentheses must be on the same line as the site name, while the right parentheses must be on a single line. For sites sharing the same configuration, we can use commas to declare multiple sites:
localhost:2020, https://site.com, http://mysite.com { ... }
When Caddy detects that the site name meets the following criteria, Let's Encrypt scripts are automatically used to add HTTPS support to the site, and ports 80 and 443 are automatically monitored:
Host name cannot be empty and there is no localhost and IP address
Port number not specified explicitly as 80
Scheme is not explicitly specified as http
TLS not turned off
Unspecified Certificate
Cache settings
We can use the expires command to set the expiration header relative to the request time. Its basic grammar is:
expires { match regex duration }
regex is a regular expression for matching request files, while duration is an expression for describing the length of time in 0y0m0d0h0i0s format. The commonly used matching grammar is:
expires { match some/path/.*.css$ 1y # expires css files in some/path after one year match .js$ 1m # expires js files after 30 days match .png$ 1d # expires png files after one day match .jpg$ 1h # expires jpg files after one hour match .pdf$ 1i # expires pdf file after one minute match .txt$ 1s # expires txt files after one second match .html$ 5i30s # expires html files after 5 minutes 30 seconds }
Reverse proxy
The proxy Directive provides basic reverse proxy functionality, which supports Health Checks and Failovers, as well as reverse proxy for WebSocket. Its basic grammar is:
proxy from to
from is the basic path for request matching, and to is the endpoint address to which the request is forwarded. We can also use more complex configurations:
proxy from to... { policy random | least_conn | round_robin | ip_hash fail_timeout duration max_fails integer try_duration duration try_interval duration health_check path health_check_interval interval_duration health_check_timeout timeout_duration header_upstream name value header_downstream name value keepalive number without prefix except ignored_paths... upstream to insecure_skip_verify preset }
Forward all requests to / api to the back-end system:
proxy /api localhost:9005
The random strategy is used to balance all requests to three back-end servers:
proxy / web1.local:80 web2.local:90 web3.local:100
Using a recycling mechanism:
proxy / web1.local:80 web2.local:90 web3.local:100 { policy round_robin }
Add health check and transparently forward host name, address and upstream:
proxy / web1.local:80 web2.local:90 web3.local:100 { policy round_robin health_check /health transparent }
Forwarding WebSocket requests:
proxy /stream localhost:8080 { websocket }
Avoid forwarding of partial static requests:
proxy / backend:1234 { except /static /robots.txt }
WebSocket
Caddy has built-in support for WebSocket connections. It allows clients to execute a simple instruction when they initiate a WebSocket connection. Its basic syntax is as follows:
websocket [path] command
We can build simple WebSocket client requests within the client:
if (window.WebSocket != undefined) { var connection = new WebSocket("ws://localhost:2015/echo"); connection.onmessage = wsMessage; connection.onopen = wsOpen; function wsOpen(event) { connection.send("Hello World"); } function wsMessage(event) { console.log(event.data); } } function wsMessage(event) { console.log(event.data); }
Then it receives the request at the server and returns the input from the client:
var readline = require('readline'); var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); rl.on('line', function(line){ console.log(line); })
Finally, the Caddy file is configured as follows:
websocket /echo "node tmp.js"
File upload
We can use the extension instruction upload provided by Caddy to build a simple file upload server:
upload path { to "directory" yes_without_tls filenames_form none|NFC|NFD filenames_in u0000–uff00 [u0000–uff00| ...] hmac_keys_in keyID_0=base64(binary) [keyID_n=base64(binary) | ...] timestamp_tolerance 0..32 silent_auth_errors }
Add the following configuration directly:
upload /web/path { to "/var/tmp" }
Then use curl to upload files:
# HTTP PUT curl \ -T /etc/os-release \ https://127.0.0.1/web/path/from-release
Or upload multiple files at the same time:
# HTTP POST curl \ -F gitconfig=@.gitconfig \ -F id_ed25519.pub=@.ssh/id_ed25519.pub \ https://127.0.0.1/web/path/
We can also use instructions to move or delete these files:
# MOVE is 'mv' curl -X MOVE \ -H "Destination: /web/path/to-release" \ https://127.0.0.1/web/path/from-release # DELETE is 'rm -r' curl -X DELETE \ https://127.0.0.1/web/path/to-release
access control
Authentication of privileges
Basic Auth
Caddy has built-in support for HTTP Basic Authentication, which forces users to access certain directories or files with specified usernames and passwords. Its basic configuration grammar is as follows:
basicauth username password { resources }
If we want to add permission authentication to all files in the / secret directory:
basicauth /secret Bob hiccup
Certain documents may also be specified:
basicauth "Mary Lou" milkshakes { /notes-for-mary-lou.txt /marylou-files /another-file.txt }
JWT
The jwt directive is an extended function of Caddy. We need to add this function on the official website and get the compiled version. Its basic grammar is as follows:
jwt path // perhaps jwt { path resource allow claim value deny claim value }
For example, we preset two tokens: user: someone and role: member. Our configuration items are as follows:
jwt { path /protected deny role member allow user someone }
The middleware will deny access to all roles: members, except for users whose user name is someone. Users of another role: admin or role: foo have normal access. We can submit tokens in three ways:
Method | Format |
---|---|
Authorization Header | Authorization: Bearer token |
Cookie | "jwt_token": token |
URL Query Parameter | /protected?token=token |
Cross-domain requests
We can use cors instructions to add cross-domain requests to servers:
cors / { origin http://allowedSite.com origin http://anotherSite.org https://anotherSite.org methods POST,PUT allow_credentials false max_age 3600 allowed_headers X-Custom-Header,X-Foobar exposed_headers X-Something-Special,SomethingElse }
We can also add JSONP support:
jsonp /api/status
For example, an endpoint returns a JSON response like {"status":"ok"} in the following format:
$ wget 'http://example.com/api/status?callback=func3022933'
It returns a response in the following format:
func3022933({"status":"ok"});
Address filtering
We can use the ipfilter instruction to allow or restrict user access based on the user's IP. Its basic syntax is:
ipfilter paths... { rule block | allow ip list or/and range of IPs... country countries ISO codes... database db_path blockpage block_page strict }
Only one IP access is allowed:
ipfilter / { rule allow ip 93.168.247.245 }
Prohibit two IP addresses from accessing a specific IP and return them to the default interface:
ipfilter / { rule block ip 192.168.0.0/16 2E80::20:F8FF:FE31:77CF/16 5.23.4.24 blockpage /local/data/default.html }
Only clients with fixed IP addresses from France are allowed to access:
ipfilter / { rule allow country FR database /local/data/GeoLite2-Country.mmdb ip 99.23.4.24 2E80::20::FEF1:91C4 }
Only client visits from the United States and Japan are supported:
ipfilter / { rule allow country US JP database /local/data/GeoLite2-Country.mmdb }
Prohibit clients from the United States and Japan from accessing / notglobal and / secret, and return to the default address directly:
ipfilter /notglobal /secret { rule block country US JP database /local/data/GeoLite2-Country.mmdb blockpage /local/data/default.html }
Request Current Limitation
We can use the ratelimit extension instruction to add the function of requesting current limit to resources. For a single resource, we can use the following instructions:
ratelimit path rate burst unit // Limit the client to initiate at most two requests per second for / r resources, with a maximum burst cap of 3 ratelimit /r 2 3 second
For multiple resources, the following instructions can be used:
ratelimit rate burst unit { resources } // Restrict access to resource files for 2 minutes ratelimit 2 2 minute { /foo.html /dir }