In the past few days, I switched the company’s test environment Nginx to Caddy, and there is still a little problem in the actual switching process, but I feel good so far, so I will record some details here.
Why switch
Most of the time we use a domain name for our production environment, and to ensure isolation we use another domain name for our test environment (test environment buy *.link
domain name, which can be filed in China and is also very cheap); however, we are not too willing to pay for the test domain name to buy a certificate, so we have been using ACME.
As we all know, the certificate of this thing needs to be renewed once every 3 months, scripted renewal and then nginx reload is sometimes not very reliable, in short, the scripted operation is still a bit risky in a complicated internal environment, so we finally decided to use Caddy once and for all.
Details involved in switching
Rule Matching
In one site we used Nginx to determine the User-Agent to handle whether the visit was mobile or desktop, and to be honest I hate this kind of stuff:
|
|
At first, I found out that Caddy also supports maps by looking up the Caddy documentation:
In the actual configuration found that this problem only requires a custom rule matcher to determine whether it is mobile or not:
When writing subsequent matching rules, we found that Caddy’s matching rules are indeed very powerful, and you can find basically From request headers to request methods, protocols, request paths, from standard matching to wildcard and regular matching, and even support code-based CEL (Common Expression Language) expressions. matching; multiple matches can also be custom named as business-related matchers
Rule Rewriting
In Nginx, the rewrite directive has multiple behaviors, such as rewriting URLs implicitly or returning redirect codes such as 301 and 307; however, in Caddy, these two behaviors are divided into two directives:
- rewrite: internal rewrite, internal replacement of URL, parameters, etc., browser address will remain unchanged
- redir: redirect, return HTTP status code for client to redirect to new page by itself
rewrite
The implicit rewrite command for addresses has the following syntax rules:
|
|
The matcher is the global standard matcher definition, you can use the built-in one, or you can combine the built-in matcher into a custom matcher, which is much more powerful than Nginx; there are three cases in to
:
-
Replace only PATH:
rewrite /abc /bcd
:In this case, rewrite determines the matching path based on the “matcher” and then completely replaces it with the last path; the last path can be referenced to the original path using the
{path}
placeholder. -
Replace only request parameters:
rewrite /api ?a=b
:In this case, Caddy uses
?
as a separator, so if?
is followed by something it means that the request parameter is replaced with a later request parameter; the last request parameter can be referenced to the original request parameter by{query}
. -
replace all:
rewrite /abc /bcd?{query}&custom=1
:In this case, Caddy replaces both the request path and the request parameters according to the “matcher” match, and of course both placeholders are available.
Note:
rewrite
only does rewrites, it does not break the request chain, which means that the final result is determined by the subsequent request matches.
redir
redir is used to declare an explicit redirect to the client, i.e. to return a specific redirect status code, with the following syntax:
|
|
The matchers are the same; <to>
This parameter is returned as the Location header, where you can use placeholders to refer to the original variables:
|
|
The code
section is divided into four cases:
- a custom status code for
3xx
temporary
: returns a 302 temporary redirectpermanent
: returns a 301 permanent redirecthtml
: redirects using the HTML document method
For example, to permanently redirect all requests to a new site:
|
|
The HTML approach here is more difficult to understand, and it originates from a specification as follows:
The redirection mechanism in the HTTP protocol is the preferred way to create redirection maps, but sometimes web developers have no control over the server or cannot configure it. For these application-specific scenarios, web developers can add an element to the carefully crafted HTML page’s
section and set the value of its http-equiv attribute to refresh. When the page is displayed, the browser will detect the element and jump to the specified page.
If the html
redirect method is used in the source code, Caddy will return an HTML page for the browser to refresh itself if the above method is used:
|
|
uri
The uri
directive is a special directive that is similar to rewrite
, except that it is more convenient for rewriting URIs, and has the following syntax:
The second parameter in the syntax is a verb that defines how to replace the URI:
strip_prefix
: removes the prefix from the pathstrip_suffix
: Remove suffixes from pathsreplace
: perform subreplacements throughout the URI path (e.g./a/b/c/d
is replaced with/a/1/2/d
)path_regexp
: perform regular replacement in paths
Here are some examples:
where replace
can be followed by a number at the end, representing how many times to find the replacement from the URI, the default is -1
that is, replace all.
WebSocket Proxy
In the Nginx configuration, if you want to proxy WebSocket links, we need to add the following settings:
But in Caddy it’s much simpler… so simple that we don’t have to do anything, it’s automatically supported:
Websocket proxying “just works” in v2; there is no need to “enable” websockets like in v1.
URL encoding
When using a path matcher, URLs are decoded by default, e.g. :
As for the reverse proxy reverse_proxy
, I haven’t encountered the encoding when passing it out yet, so I need to test it
Forced HTTP
Some sites may be HTTP by default, and we don’t expect to access them by HTTPS; however, Caddy will apply for ACME certificate for the site by default, and we can’t access them if we can’t apply for the certificate; in this case, we just need to write the HTTP protocol on the site address forcibly:
Proxy HTTPS
If you want to proxy HTTPS service, you only need to fill in the HTTPS address in reverse_proxy
; but unlike Nginx, Caddy’s TLS checksum is on by default, so if the backend HTTPS certificate is expired, it may cause Caddy to return a 502 error; this can be turned off by using transport
:
Custom Certificates
If you already have your own certificate and do not expect Caddy to apply it automatically, just add the certificate after the tls
command:
Log Printing
Caddy’s logging system is completely different from Nginx, Caddy logs are divided by Namespace, by default only the request log of the current site can be printed in the site configuration, if you need to print the upstream address of the reverse proxy, for example, you need to configure it in the global logging configuration. If you know go, you can take a look at uber-go/zap logging framework; here is a sample of printing request logs and upstream logs separately by file:
|
|
TLS versions are not supported
Unfortunately we have a TLS 1.1 compatible service and when switching to Caddy TLS 1.1 is no longer supported, currently Caddy has a minimum TLS 1.2 and a maximum TLS 1.3 compatibility:
|
|
protocols specifies the minimum and maximum protocol versions. Default min: tls1.2. Default max: tls1.3
Summary
To sum up: the matcher is comfortable, the configuration behavior is clear, the configuration reference is written in 10,000 lines less, and the rest of the pitfalls continue to be stepped on.
Reference https://mritd.com/2021/08/20/switching-rrom-nginx-too-caddy/