selfhosted-apps-docker/caddy_v2/readme.md

740 lines
21 KiB
Markdown
Raw Normal View History

2020-04-27 21:04:28 +00:00
# Caddy v2 Reverse Proxy
2020-05-18 22:49:18 +00:00
###### guide-by-example
2020-04-27 21:04:28 +00:00
![logo](https://i.imgur.com/xmSY5qu.png)
2020-05-14 22:07:29 +00:00
2020-05-14 22:10:24 +00:00
1. [Purpose & Overview](#Purpose--Overview)
2020-05-14 22:08:37 +00:00
2. [Caddy as a reverse proxy in docker](#Caddy-as-a-reverse-proxy-in-docker)
3. [Caddy more info and various configurations](#Caddy-more-info-and-various-configurations)
4. [Caddy DNS challenge](#Caddy-DNS-challenge)
2020-05-14 22:07:29 +00:00
# Purpose & Overview
2022-10-22 10:48:08 +00:00
Reverse proxy is needed if one wants access to services based on the hostname.<br>
For example `nextcloud.example.com` points traffic to nextcloud docker container,
while `jellyfin.example.com` points to the media server on the network.
2020-05-14 22:07:29 +00:00
2020-05-14 06:36:21 +00:00
* [Official site](https://caddyserver.com/v2)
* [Official documentation](https://caddyserver.com/docs/)
* [Forum](https://caddy.community/)
* [Github](https://github.com/caddyserver/caddy)
2022-10-22 10:48:08 +00:00
Caddy is a pretty damn good web server with automatic HTTPS. Written in Go.<br>
2020-05-14 22:07:29 +00:00
Web servers are build to deal with http traffic, so they are an obvious choice
2022-10-22 10:48:08 +00:00
for the function of reverse proxy.<br>
2020-05-14 22:07:29 +00:00
In this setup Caddy is used mostly as
2022-10-22 10:48:08 +00:00
[a TLS termination proxy](https://www.youtube.com/watch?v=H0bkLsUe3no).
2020-05-14 22:07:29 +00:00
Https encrypted tunel ends with it, so that the traffic can be analyzed
2021-03-02 22:44:33 +00:00
and send to a correct webserver based on the settings in `Caddyfile`.
2020-05-14 22:07:29 +00:00
2021-02-22 22:25:12 +00:00
Caddy with its build-in https and and simple config approach
allows even most trivial configs to just work:
2020-05-14 22:07:29 +00:00
```
2022-10-22 10:48:08 +00:00
nextcloud.{$MY_DOMAIN} {
reverse_proxy nextcloud-web:80
2020-05-14 22:07:29 +00:00
}
2020-04-27 21:04:28 +00:00
2022-10-22 10:48:08 +00:00
jellyfin.{$MY_DOMAIN} {
2020-05-14 22:07:29 +00:00
reverse_proxy 192.168.1.20:80
}
```
2022-10-22 10:59:22 +00:00
And **just works** means fully works. No additional configuration needed
2022-10-22 10:48:08 +00:00
for https redirect, or special services if target is not a container,
or need to deal with load balancer, or need to add boilerplate headers
for x-forward, or other extra work.<br>
2022-10-31 07:54:58 +00:00
It has great out of the box defaults, fitting majority of uses
2022-10-22 11:01:07 +00:00
and only some special casess with extra functionality need extra work.
2022-10-22 10:48:08 +00:00
![url](https://i.imgur.com/iTjzPc0.png)
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
# Caddy as a reverse proxy in docker
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
Caddy will be running as a docker container and will route traffic to other containers,
2020-04-30 18:59:06 +00:00
or machines on the network.
2020-04-27 21:04:28 +00:00
2020-04-29 00:16:57 +00:00
### - Requirements
2020-04-27 21:04:28 +00:00
2020-05-23 11:48:14 +00:00
* have some basic linux knowledge, create folders, create files, edit files, run scripts,...
2020-04-28 23:50:40 +00:00
* have a docker host and some vague docker knowledge
* have port 80 and 443 forwarded on the router/firewall to the docker host
2022-10-22 10:48:08 +00:00
* have a domain, `example.com`, you can buy one for 2€ annually on namecheap
* have correctly set type-A DNS records pointing at your public IP address,
[switching to Cloudflare](https://youtu.be/XQKkb84EjNQ) for DNS managment is recommended
2020-04-27 21:04:28 +00:00
2020-04-29 00:16:57 +00:00
### - Files and directory structure
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
```
2020-04-30 21:48:24 +00:00
/home/
└── ~/
└── docker/
└── caddy/
├── config/
├── data/
├── .env
├── Caddyfile
└── docker-compose.yml
2020-04-28 23:50:40 +00:00
```
2020-04-27 21:04:28 +00:00
2020-05-05 15:39:05 +00:00
* `config/` - a directory containing configs that Caddy generates,
most notably `autosave.json` which is a backup of the last loaded config
2020-05-05 15:39:05 +00:00
* `data/` - a directory storing TLS certificates
* `.env` - a file containing environment variables for docker compose
* `Caddyfile` - the Caddy configuration file
* `docker-compose.yml` - a docker compose file, telling docker how to run containers
2020-04-27 21:04:28 +00:00
2020-05-28 23:06:45 +00:00
You only need to provide the three files.<br>
2020-05-05 15:39:05 +00:00
The directories are created by docker compose on the first run,
the content of these is visible only as root of the docker host.
2020-04-27 21:04:28 +00:00
2020-04-29 00:16:57 +00:00
### - Create a new docker network
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
`docker network create caddy_net`
All the containers and Caddy must be on the same network.
2020-04-27 21:04:28 +00:00
2020-04-30 18:59:06 +00:00
### - Create .env file
2020-04-27 21:04:28 +00:00
2022-10-22 10:59:22 +00:00
You want to change `example.com` to your domain.
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
`.env`
```bash
2022-10-22 10:59:22 +00:00
MY_DOMAIN=example.com
2020-05-20 18:29:12 +00:00
DOCKER_MY_NETWORK=caddy_net
2020-04-28 23:50:40 +00:00
```
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
Domain names, api keys, email settings, ip addresses, database credentials, ...
whatever is specific for one deployment and different for another,
all of that ideally goes in to the `.env` file.
2020-04-29 00:16:57 +00:00
If `.env` file is present in the directory with the compose file,
it is automatically loaded and these variables will be available
for docker-compose when building the container with `docker-compose up`.
2020-04-28 23:50:40 +00:00
This allows compose files to be moved from system to system more freely
and changes are done to the `.env` file.
Often variable should be available also inside the running container.
For that it must be declared in the `environment` section of the compose file,
2020-04-30 18:59:06 +00:00
as can be seen next in Caddie's `docker-compose.yml`
2020-04-28 23:50:40 +00:00
2020-05-28 23:06:45 +00:00
*extra info:*<br>
2020-04-28 23:50:40 +00:00
`docker-compose config` shows how compose will look
with the variables filled in.
2020-04-29 00:16:57 +00:00
### - Create docker-compose.yml
2020-04-28 23:50:40 +00:00
`docker-compose.yml`
```yml
services:
caddy:
image: caddy
2020-05-14 15:53:53 +00:00
container_name: caddy
hostname: caddy
2020-04-28 23:50:40 +00:00
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- MY_DOMAIN
volumes:
2022-08-13 11:37:52 +00:00
- ./Caddyfile:/etc/caddy/Caddyfile
2020-04-28 23:50:40 +00:00
- ./data:/data
- ./config:/config
networks:
default:
2022-05-14 10:46:06 +00:00
name: $DOCKER_MY_NETWORK
external: true
2020-04-28 23:50:40 +00:00
```
2020-05-23 11:48:14 +00:00
* port 80 and 443 are pusblished for http and https
2020-04-30 18:59:06 +00:00
* MY_DOMAIN variable is passed in to the container so that it can be used
in `Caddyfile`
2022-09-13 07:02:53 +00:00
* the `Caddyfile` is bind-mounted from the docker host
2020-04-30 18:59:06 +00:00
* directories `data` and `config` are bind mounted so that their content persists
* the same network is joined as for all other containers
2020-04-28 23:50:40 +00:00
2020-04-29 00:16:57 +00:00
### - Create Caddyfile
2020-04-28 23:50:40 +00:00
`Caddyfile`
```
{
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
a.{$MY_DOMAIN} {
reverse_proxy whoami:80
}
b.{$MY_DOMAIN} {
reverse_proxy nginx:80
}
```
2022-10-22 10:48:08 +00:00
`a` and `b` are the subdomains `a.example.com` and `b.example.com`,
can be named whatever.
2020-04-28 23:50:40 +00:00
For them to work they must have type-A DNS record
2020-05-28 23:06:45 +00:00
pointing at your public ip set on Cloudflare, or wherever the domains DNS is managed.<br>
2020-04-28 23:50:40 +00:00
2020-05-28 23:06:45 +00:00
The value of `{$MY_DOMAIN}` is provided by the compose and the `.env` file.<br>
2020-04-29 00:16:57 +00:00
The subdomains point at docker containers by their **hostname** and **exposed port**.
2020-05-28 23:06:45 +00:00
So every docker container you spin should have hostname definied.<br>
2022-09-13 07:02:53 +00:00
Commented out is the staging url for let's encrypt.
2020-04-28 23:50:40 +00:00
2020-04-29 00:16:57 +00:00
### - Setup some docker containers
2020-04-28 23:50:40 +00:00
2020-05-28 23:06:45 +00:00
Something light and easy to setup to route to.<br>
2020-04-28 23:50:40 +00:00
Assuming for this testing these compose files are in the same directory with Caddy,
so they make use of the same `.env` file and so be on the same network.
2020-04-30 18:59:06 +00:00
Note the lack of published/mapped ports in the compose,
2020-05-28 23:06:45 +00:00
as they will be accessed only through Caddy, which has it's ports published.<br>
2020-04-30 18:59:06 +00:00
And since the containers and Caddy are all on the same bridge docker network,
2020-05-28 23:06:45 +00:00
they can access each other on any port.<br>
2020-04-30 18:59:06 +00:00
Exposed ports are just documentation,
[don't confuse expose and publish](https://maximorlov.com/exposing-a-port-in-docker-what-does-it-do/).
2020-04-28 23:50:40 +00:00
2020-05-28 23:06:45 +00:00
*extra info:*<br>
2020-05-20 17:20:01 +00:00
To know which ports containers have exposed - `docker ps`, or
`docker port <container-name>`, or use [ctop](https://github.com/bcicen/ctop).
2020-04-28 23:50:40 +00:00
`whoami-compose.yml`
```yaml
version: "3.7"
services:
whoami:
image: "containous/whoami"
container_name: "whoami"
hostname: "whoami"
networks:
default:
2022-05-14 10:46:06 +00:00
name: $DOCKER_MY_NETWORK
external: true
2020-04-28 23:50:40 +00:00
```
`nginx-compose.yml`
```yaml
version: "3.7"
services:
nginx:
image: nginx:latest
container_name: nginx
hostname: nginx
networks:
default:
2022-05-14 10:46:06 +00:00
name: $DOCKER_MY_NETWORK
external: true
2020-04-28 23:50:40 +00:00
```
2020-04-29 00:16:57 +00:00
### - editing hosts file
2020-04-28 23:50:40 +00:00
2020-05-16 20:11:36 +00:00
You are on your local network and you are likely running the docker host
2020-05-28 23:06:45 +00:00
inside the same network.<br>
If that's the case then shit will not work without editing the hosts file.<br>
2022-10-31 07:54:58 +00:00
Reason being that when you write that `a.example.com` in to your browser,
you are asking google's DNS for `a.example.com` IP address.
2020-05-16 20:11:36 +00:00
It will give you your own public IP, and most routers/firewalls wont allow
this loopback, where your requests should go out and then right back.
So just [edit](https://support.rackspace.com/how-to/modify-your-hosts-file/)
`hosts` as root/administrator,
2020-04-28 23:50:40 +00:00
adding whatever is the local IP of the docker host and the hostname:
2020-05-16 20:11:36 +00:00
```
2022-10-22 03:48:26 +00:00
192.168.1.222 a.{$MY_DOMAIN}
192.168.1.222 b.{$MY_DOMAIN}
2020-05-16 20:11:36 +00:00
```
2020-04-27 21:04:28 +00:00
2020-05-16 20:11:36 +00:00
If it is just quick testing one can use Opera browser
2020-05-28 23:06:45 +00:00
and enable the build in VPN.<br>
2020-05-14 06:36:21 +00:00
2020-05-16 20:11:36 +00:00
One can also run a dns/dhcp server on the network, to solve this for all
2020-05-28 23:06:45 +00:00
devices.<br>
2020-05-18 22:50:11 +00:00
Here's a [guide-by-example for dnsmasq](
2020-05-16 20:19:27 +00:00
https://github.com/DoTheEvo/selfhosted-apps-docker/tree/master/dnsmasq).
2020-04-27 21:04:28 +00:00
2020-04-29 00:16:57 +00:00
### - Run it all
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
Caddy
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
* `docker-compose up -d`
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
Services
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
* `docker-compose -f whoami-compose.yml up -d`
* `docker-compose -f nginx-compose.yml up -d`
2020-04-27 21:04:28 +00:00
2020-05-14 15:53:53 +00:00
Give it time to get certificates, checking `docker logs caddy` as it goes,
2020-04-28 23:50:40 +00:00
then visit the urls. It should lead to the services with https working.
2020-04-27 21:04:28 +00:00
2020-05-28 23:06:45 +00:00
If something is fucky use `docker logs caddy` to see what is happening.<br>
2020-05-14 15:53:53 +00:00
Restarting the container `docker container restart caddy` can help.
Or investigate inside `docker exec -it caddy /bin/sh`.
2020-04-28 23:50:40 +00:00
For example trying to ping hosts that are suppose to be reachable,
`ping nginx` should work.
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
There's also other possible issues, like bad port forwarding towards docker host.
2020-04-27 21:04:28 +00:00
2020-05-28 23:06:45 +00:00
*extra info:*<br>
2020-05-14 15:53:53 +00:00
`docker exec -w /etc/caddy caddy caddy reload` reloads config
2020-05-14 06:36:21 +00:00
if you made changes and want them to take effect.
2020-05-05 15:39:05 +00:00
2022-08-13 11:37:52 +00:00
*extra info2:*<br>
caddy can complain about formatting of the `Caddyfile`<br>
this executed on the host will let caddy overwrite the Caddyfile with
correct formatting
`docker exec -w /etc/caddy caddy caddy fmt -overwrite`
2020-04-28 23:50:40 +00:00
# Caddy more info and various configurations
2020-04-27 21:04:28 +00:00
2020-05-21 22:06:39 +00:00
##### Caddyfile structure:
2020-04-28 23:50:40 +00:00
![caddyfile-diagram-pic](https://i.imgur.com/c0ycNal.png)
2020-04-27 21:04:28 +00:00
2020-05-21 22:06:39 +00:00
Worth having a look at the official documentation, especially these short pages
2020-04-27 21:04:28 +00:00
2020-05-21 22:06:39 +00:00
* [concept](https://caddyserver.com/docs/caddyfile/concepts)
2020-04-28 23:50:40 +00:00
* [conventions](https://caddyserver.com/docs/conventions)
2020-05-21 22:06:39 +00:00
* [reverse_proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy)
2020-04-27 21:04:28 +00:00
2020-05-25 21:49:04 +00:00
Maybe checking out
[mozzila's - overview of HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview)
would also not hurt, it is very well written.
2020-04-28 23:50:40 +00:00
2020-04-30 23:51:39 +00:00
### Routing traffic to other machines on the LAN
2020-04-28 23:50:40 +00:00
2020-05-28 23:06:45 +00:00
If not targeting a docker container but a dedicated machine on the network.<br>
2020-04-30 23:51:39 +00:00
Nothing really changes, if you can ping the machine from Caddy container
by its hostname or its IP, it will work.
```
blue.{$MY_DOMAIN} {
reverse_proxy server-blue:80
}
violet.{$MY_DOMAIN} {
reverse_proxy 192.168.1.100:80
}
```
### Reverse proxy without domain and https
You can always just use localhost, which will translates in to docker hosts IP address.
2020-04-28 23:50:40 +00:00
```
localhost:55414 {
reverse_proxy urbackup:55414
}
:9090 {
reverse_proxy prometheus:9090
}
```
2020-05-28 23:06:45 +00:00
Prometheus entry uses short-hand notation.<br>
2020-04-29 00:16:57 +00:00
TLS is automatically disabled in localhost use.
2020-04-28 23:50:40 +00:00
2020-04-30 18:59:06 +00:00
But for this to work Caddy's compose file needs to have those ports **published** too.
`docker-compose.yml`
```yml
services:
caddy:
image: caddy
2020-05-14 15:53:53 +00:00
container_name: caddy
hostname: caddy
2020-04-30 18:59:06 +00:00
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "55414:55414"
- "9090:9090"
environment:
- MY_DOMAIN
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data
- ./config:/config
networks:
default:
2022-05-14 10:46:06 +00:00
name: $DOCKER_MY_NETWORK
external: true
2020-04-30 18:59:06 +00:00
```
With this setup, and assuming docker host at: `192.168.1.222`,
2020-04-28 23:50:40 +00:00
writing `192.168.1.222:55414` in to browser will go to to urbackup,
and `192.168.1.222:9090` gets to prometheus.
2020-05-25 21:49:04 +00:00
### Named matchers and IP filtering
Caddy has [matchers](https://caddyserver.com/docs/caddyfile/matchers)
2020-05-28 23:06:45 +00:00
which allow you to define how to deal with incoming requests.<br>
2020-05-25 21:49:04 +00:00
`reverse_proxy server-blue:80` is a matcher that matches all requests
2020-05-28 23:06:45 +00:00
and sends them somewhere.<br>
2020-05-25 21:55:17 +00:00
But if more control is desired, path matchers and named matchers come to play.
2020-05-25 21:49:04 +00:00
2020-05-25 21:55:17 +00:00
What if you want to block all traffic coming from the outside world,
2020-05-28 23:06:45 +00:00
but local network be allowed through?<br>
2020-05-25 21:49:04 +00:00
Well, the [remote_ip](https://caddyserver.com/docs/caddyfile/matchers#remote-ip)
2020-05-28 23:06:45 +00:00
matcher comes to play, which enables you to filter requests by their IP.<br>
2020-05-25 21:49:04 +00:00
Named matchers are defined by `@` and can be named whatever you like.
```
{
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
a.{$MY_DOMAIN} {
reverse_proxy whoami:80
}
b.{$MY_DOMAIN} {
reverse_proxy nginx:80
@fuck_off_world {
not remote_ip 192.168.1.0/24
}
respond @fuck_off_world 403
}
```
2020-05-28 23:06:45 +00:00
`@fuck_off_world` matches all IPs except the local network IP range.<br>
2020-05-25 21:49:04 +00:00
Requests matching that rule get the response 403 - forbidden.
### Snippets
What if you need to have the same matcher in several site-blocks and
would prefer for config to look cleaner?
2020-05-28 23:06:45 +00:00
Here comes the [snippets](https://caddyserver.com/docs/caddyfile/concepts#snippets).<br>
2020-05-25 21:55:17 +00:00
Snippets are defined under the global options block,
2020-05-28 23:06:45 +00:00
using parentheses, named whatever you like.<br>
2020-05-25 21:55:17 +00:00
They then can be used inside any site-block with simple `import <snippet name>`
2020-05-25 21:49:04 +00:00
2020-05-25 21:55:17 +00:00
Now would be a good time to look again at that concept picture above.
2020-05-25 21:49:04 +00:00
2020-05-25 21:55:17 +00:00
Here is above example of IP filtering named matcher done using a snippet.
2020-05-25 21:49:04 +00:00
```
{
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
(LAN_only) {
@fuck_off_world {
not remote_ip 192.168.1.0/24
}
respond @fuck_off_world 403
}
a.{$MY_DOMAIN} {
reverse_proxy whoami:80
}
b.{$MY_DOMAIN} {
reverse_proxy nginx:80
import LAN_only
}
```
2020-04-29 00:16:57 +00:00
### Backend communication
2020-04-28 23:50:40 +00:00
Some containers might be set to communicate only through https 443 port.
But since they are behind proxy, their certificates wont be singed, wont be trusted.
2020-05-28 23:06:45 +00:00
Caddies sub-directive `transport` sets how to communicate with the backend.<br>
2020-05-14 15:53:53 +00:00
Setting the upstream's scheme to `https://`
or declaring the `tls` transport subdirective makes it use https.
Setting `tls_insecure_skip_verify` makes Caddy ignore errors due to
untrusted certificates coming from the backend.
2020-04-28 23:50:40 +00:00
```
2020-05-14 22:07:29 +00:00
whatever.{$MY_DOMAIN} {
reverse_proxy https://server-blue:443 {
2020-04-28 23:50:40 +00:00
transport http {
2020-05-31 08:27:59 +00:00
tls
2020-04-28 23:50:40 +00:00
tls_insecure_skip_verify
}
}
}
```
2020-04-30 18:59:06 +00:00
### HSTS and redirects
2021-06-11 17:47:18 +00:00
Here is an example of a redirect when wanting the common case of
2021-06-11 17:47:48 +00:00
switching anyone that comes to a `www` subdomain to the naked domain.
2021-06-11 17:37:31 +00:00
```
www.{$MY_DOMAIN} {
redir https://{$MY_DOMAIN}{uri}
}
```
2021-06-11 17:47:18 +00:00
Another example is running NextCloud behind proxy,
2021-06-11 17:37:31 +00:00
which likely shows few warning on its status page.
2020-04-30 18:59:06 +00:00
It requires some redirects for service discovery to work and would like
2022-05-04 22:40:46 +00:00
if [HSTS](https://www.youtube.com/watch?v=kYhMnw4aJTw)
[2](https://www.youtube.com/watch?v=-MWqSD2_37E) would be set.<br>
2020-04-28 23:50:40 +00:00
Like so:
```
nextcloud.{$MY_DOMAIN} {
reverse_proxy nextcloud:80
header Strict-Transport-Security max-age=31536000;
redir /.well-known/carddav /remote.php/carddav 301
redir /.well-known/caldav /remote.php/caldav 301
}
```
2020-05-14 22:07:29 +00:00
### Headers and gzip
2020-04-30 18:59:06 +00:00
This example is with bitwarden_rs password manager, which comes with its reverse proxy
2020-04-28 23:50:40 +00:00
[recommendations](https://github.com/dani-garcia/bitwarden_rs/wiki/Proxy-examples).
2020-05-28 23:06:45 +00:00
`encode gzip` enables compression.<br>
2020-04-28 23:50:40 +00:00
This lowers the bandwith use and speeds up loading of the sites.
It is often set on the webserver running inside the docker container,
but if not it can be enabled on caddy.
You can check if your stuff has it enabled by using one of
[many online tools](https://varvy.com/tools/gzip/)
2020-05-14 23:12:32 +00:00
By default, Caddy passes through Host header and adds X-Forwarded-For
for the client IP. This means that 90% of the time a simple config
is all that is needed but sometimes some extra headers might be desired.
2020-05-28 23:06:45 +00:00
Here we see bitwarden make use of some extra headers.<br>
2020-05-14 23:12:32 +00:00
We can also see its use of websocket protocol for notifications at port 3012.
2020-04-28 23:50:40 +00:00
```
bitwarden.{$MY_DOMAIN} {
encode gzip
header {
2020-04-28 23:50:40 +00:00
# Enable cross-site filter (XSS) and tell browser to block detected attacks
X-XSS-Protection "1; mode=block"
# Disallow the site to be rendered within a frame (clickjacking protection)
X-Frame-Options "DENY"
# Prevent search engines from indexing (optional)
X-Robots-Tag "none"
# Server name removing
-Server
}
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
# Notifications redirected to the websockets server
reverse_proxy /notifications/hub bitwarden:3012
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
# Proxy the Root directory to Rocket
reverse_proxy bitwarden:80
}
```
2020-04-27 21:04:28 +00:00
2020-05-14 22:07:29 +00:00
### Basic authentication
2020-05-28 23:06:45 +00:00
[Official documentation.](https://caddyserver.com/docs/caddyfile/directives/basicauth)<br>
2020-05-14 22:07:29 +00:00
Directive `basicauth` can be used when one needs to add
a username/password check before accessing a service.
Password is [bcrypt](https://www.devglan.com/online-tools/bcrypt-hash-generator) hashed
2020-05-28 23:06:45 +00:00
and then [base64](https://www.base64encode.org/) encoded.<br>
2020-05-14 22:07:29 +00:00
You can use the [`caddy hash-password`](https://caddyserver.com/docs/command-line#caddy-hash-password)
command to hash passwords for use in the config.
Config bellow has login/password : `bastard`/`bastard`
`Caddyfile`
```
b.{$MY_DOMAIN} {
reverse_proxy whoami:80
basicauth {
bastard JDJhJDA0JDVkeTFJa1VjS3pHU3VHQ2ZSZ0pGMU9FeWdNcUd0Wk9RdWdzSzdXUXNhWFFLWW5pYkxXVEU2
}
}
```
2020-04-30 18:59:06 +00:00
### Logging
2020-05-28 23:06:45 +00:00
[Official documentation.](https://caddyserver.com/docs/caddyfile/directives/log)<br>
2020-04-28 23:50:40 +00:00
If access logs for specific site are desired
2020-04-27 21:04:28 +00:00
2020-04-28 23:50:40 +00:00
```
bookstack.{$MY_DOMAIN} {
log {
output file /data/logs/bookstack_access.log {
roll_size 20mb
roll_keep 5
}
}
reverse_proxy bookstack:80
2020-04-28 23:50:40 +00:00
}
```
2020-04-27 21:04:28 +00:00
# Caddy DNS challenge
2020-05-14 22:07:29 +00:00
This setup only works for Cloudflare.
2022-10-22 03:48:26 +00:00
DNS challenge authenticates ownership of the domain by requesting that the owner
puts a specific TXT record in to the domains DNS zone.<br>
Benefit of using DNS challenge is that there is no need for your server
to be reachable by the letsencrypt servers. Cant open ports or want to exclude
entire world except your own country from being able to reach your server?
DNS challange is what you want to use for https then.<br>
2022-10-22 04:14:33 +00:00
It also allows for issuance of wildcard certificates.<br>
2022-10-22 03:48:26 +00:00
The drawback is a potential security issue, since you are creating a token
that allows full control over your domain's DNS. You store this token somewhere,
you are giving it to some application from dockerhub...
2020-05-14 23:12:32 +00:00
2022-10-22 03:48:26 +00:00
*note*: caddy uses a new [libdns](https://github.com/libdns/libdns/)
golang library with [cloudflare package](https://github.com/libdns/cloudflare)
2020-05-14 23:12:32 +00:00
2020-05-14 22:07:29 +00:00
### - Create API token on Cloudflare
2022-10-22 03:48:26 +00:00
[On Cloudflare](https://dash.cloudflare.com/profile/api-tokens)
create a new API Token with two permsisions,
2020-05-14 23:29:30 +00:00
[pic of it here](https://i.imgur.com/YWxgUiO.png)
2020-05-14 22:07:29 +00:00
2020-05-28 23:06:45 +00:00
* zone/zone/read<br>
* zone/dns/edit<br>
2020-05-14 23:31:09 +00:00
Include all zones needs to be set.
2020-05-14 22:07:29 +00:00
2022-10-22 04:14:33 +00:00
### - Edit .env file
Add `CLOUDFLARE_API_TOKEN` variable with the value of the newly created token.
`.env`
```
MY_DOMAIN={$MY_DOMAIN}
DOCKER_MY_NETWORK=caddy_net
CLOUDFLARE_API_TOKEN=<cloudflare api token goes here>
```
2020-05-14 22:07:29 +00:00
### - Create Dockerfile
2020-05-14 23:26:58 +00:00
To add support, Caddy needs to be compiled with
2020-05-28 23:06:45 +00:00
[Cloudflare DNS plugin](https://github.com/caddy-dns/cloudflare).<br>
2020-05-14 23:26:58 +00:00
This is done by using your own Dockerfile, using the `builder` image.
2022-10-22 03:48:26 +00:00
Create a directory `dockerfile-caddy` in the caddy directory.<br>
2020-05-14 22:07:29 +00:00
Inside create a file named `Dockerfile`.
2020-05-14 23:12:32 +00:00
`Dockerfile`
```Dockerfile
2022-10-22 03:48:26 +00:00
FROM caddy:2.6.2-builder AS builder
2022-10-22 03:48:26 +00:00
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare
2020-04-27 21:04:28 +00:00
2022-10-22 03:48:26 +00:00
FROM caddy:2.6.2
2020-04-27 21:04:28 +00:00
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```
2020-05-14 22:07:29 +00:00
### - Edit docker-compose.yml
2020-05-28 23:06:45 +00:00
`image` replaced with `build` option pointing at the `Dockerfile` location<br>
2020-05-14 22:07:29 +00:00
and `CLOUDFLARE_API_TOKEN` variable added.
`docker-compose.yml`
```yml
services:
caddy:
2022-10-22 03:48:26 +00:00
build: ./dockerfile-caddy
2020-05-14 22:07:29 +00:00
container_name: caddy
hostname: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- MY_DOMAIN
- CLOUDFLARE_API_TOKEN
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data
- ./config:/config
networks:
default:
2022-05-14 10:46:06 +00:00
name: $DOCKER_MY_NETWORK
external: true
2020-05-14 22:07:29 +00:00
```
### - Edit Caddyfile
2022-10-22 04:14:33 +00:00
Add global option `acme_dns`<br>
2022-10-22 03:48:26 +00:00
or add `tls` directive to the site-blocks.
2020-04-27 21:04:28 +00:00
2020-04-30 18:59:06 +00:00
`Caddyfile`
```
2020-05-14 22:07:29 +00:00
{
2022-10-22 03:48:26 +00:00
acme_dns cloudflare {$CLOUDFLARE_API_TOKEN}
2020-05-14 22:07:29 +00:00
}
a.{$MY_DOMAIN} {
2020-04-30 18:59:06 +00:00
reverse_proxy whoami:80
2020-05-14 22:07:29 +00:00
}
b.{$MY_DOMAIN} {
reverse_proxy nginx:80
tls {
2022-10-22 03:48:26 +00:00
dns cloudflare {$CLOUDFLARE_API_TOKEN}
}
}
```
### - Wildcard certificate
2022-10-22 04:14:33 +00:00
A one certificate to rule all subdomains. But not apex/naked domain, thats separate.<br>
2022-10-22 03:48:26 +00:00
As shown in [the documentation](https://caddyserver.com/docs/caddyfile/patterns#wildcard-certificates),
2022-10-22 04:14:33 +00:00
the subdomains must be moved under the wildcard site block and make use
of host matching and handles.
2022-10-22 03:48:26 +00:00
`Caddyfile`
```
{
acme_dns cloudflare {$CLOUDFLARE_API_TOKEN}
}
{$MY_DOMAIN} {
reverse_proxy homer:8080
}
*.{$MY_DOMAIN} {
@a host a.{$MY_DOMAIN}
handle @a {
reverse_proxy whoami:80
}
@b host b.{$MY_DOMAIN}
handle @b {
reverse_proxy nginx:80
}
handle {
abort
2020-04-27 21:04:28 +00:00
}
2020-04-30 18:59:06 +00:00
}
```
2020-05-14 22:07:29 +00:00
2022-10-22 03:48:26 +00:00
[Here's](https://github.com/caddyserver/caddy/issues/3200) some discussion
2022-11-07 20:52:40 +00:00
on this and a simple, elegant way we could have had, without the need to
2022-10-22 03:48:26 +00:00
dick with the Caddyfile this much. Just one global line declaration.<br>
2022-11-07 20:52:40 +00:00
But the effort went sideways.<br>
So I myself do not even bother with wildcard when the config ends up looking
like nginx full on boilerplate lines.