How to Configure Caddy for Remote Administration
Intro
Caddy is a powerful web server that offers a REST API for dynamic configuration. In complex use-cases such as AppMasker's, you might want to configure your web server(s) from a centralized location. To make connecting to its API from foreign clients secure, Caddy uses an authentication strategy called "mutual TLS", or mTLS.
mTLS
When connecting to a web server, a client (such as your browser) is presented a TLS certificate to prove the server is who they say they are and thus securing the connection. With mTLS, the client (another server rather than a web browser)also presents a certificate to identify itself. Thus both parties are able to authenticate with one-another.
Certificates
So what's this look like with Caddy? The Caddy instance that is exposing its admin API will have a TLS certificate, as usual. Its config will include a public key that it trusts. Then, the server that is calling the API will include a public / private key pair in the request.
Outcomes
This guide assumes you have basic working knowledge of Caddy. Given that, by the end of this guide, you'll have enabled an instance of Caddy to be remotely configured by:
- Creating a public / private key pair
- Writing a Caddy JSON config that enables remote administration
- Calling Caddy's admin API with the public / private key pair attached in your request
1. Creating the Certificates
We need to create a public (also called the "certificate") / private key pair for our client to use to securely connect to Caddy's remote admin API. To do this, we use a command-line tool called Open SSL. Make sure to install it for your operating system. Then run this command (this works on MacOS, YMMV):
openssl req -x509 -sha256 -inform der -nodes -days 7300 -newkey rsa:4096 -keyout key.pem -out cert.pem
Follow the prompts that are returned. This command will choose the algorithms by which the keys are generated ("-x509", "-sha256"), the input format ("-inform der"), disables encryption for the private key ("-nodes"), indicates that this key will expire in 20 years ("-days 7300") and outputs our private key (key.pem) and public key (cert.pem) in the current directory. You can learn more about this command here. Hang on to the outputted files!
One caveat: Caddy's config only wants the contents of the cert.pem, and not the headers. In other words, you won't use the "BEGIN CERTIFICATE" and "END CERTIFICATE" headers in your config file. Open cert.pem in a text editor to see what I mean.
2. Writing the Caddy Config
Next, your instance of Caddy needs to be configured to have your public key that you generated in step 1. Instead of a Caddyfile, we'll be using the native JSON config. The snippet below covers only the admin settings:
"admin": {
"identity": {
"identifiers": [
"example.com",
]
},
"remote": {
"listen": ":2021",
"access_control": [
{
"public_keys": [
"<replace this string with your cert.pem contents (excluding the headers!)>"
]
}
]
}
},
Be sure to replace "example.com" with the domain of your web server. This takes care of Caddy's side of the mTLS transaction by getting a TLS certificate for the server. Also, you can listen on whatever port you want; the default is 2021.
Then tell Caddy to load your updated config:
caddy reload --config config.json
If you're not sure how to write the rest of the config, a nifty trick is to write out your normal config with the more approachable Caddyfile format and use the following command to output a JSON version. Then you can merge this output with the JSON for the admin configuration above. Here are the JSON config structure docs.
caddy adapt --config Caddyfile
3. Call Caddy's API
The last step is to properly call Caddy's admin endpoint from your service. This will vary quite a bit depending the language and client library you're using. We basically need to attach the certificate file and private key file that we generated in step one. A Google search for "<your http client> mtls" or "<your http client> client certificate" should help.
Here's what that looks like with cUrl:
curl --cert cert.pem --cert-type PEM \
--key key.pem --key-type PEM https://example.com:2021/config
In this example, we've made a GET call to /config to see Caddy's current config. Here are the other available endpoints.
Here's a second example, this time using the Axios http package for Node.js (with Typescript).
import axios from 'axios';
import * as https from 'https';
const cert: string = fs.readFileSync('./cert.pem');
const key: string = fs.readFileSync('./key.pem');
const httpsAgent = new https.Agent({
cert,
key
});
const response = await axios.request({
url: https://example.com:2021/config,
method: 'GET',
httpsAgent
});
And that's it! You're now able to reach Caddy's admin API from a remote client, securely.
What's Next
If you're interested in remotely configuring Caddy, you probably want to have Caddy load its config on startup from your app that is handling configuration.
If your company would like Caddy managed for you, contact us below.