This is a development instance of xesite. Things here are probably unfinished or in drafting. Don't take anything here super seriously. If you want to share this to an online aggregator, please don't. Drafts are not finalized yet for a reason. Please don't be the reason I need to implement more advanced security than just obscurity.

Site to Site WireGuard: Part 4 - HTTPS

Read time in minutes: 12

This is the fourth post in my Site to Site WireGuard VPN series. You can read the other articles here:

In this article, we are going to install Caddy and set up the following:

  • A plaintext markdown site to demonstrate the process
  • A URL shortener at https://g.o/ (with DNS and TLS certificates too)

HTTPS and Caddy

Caddy is a general-purpose HTTP server. One of its main features is automatic Let's Encrypt support. We are using it here to serve HTTPS because it has a very, very simple configuration file format.

Caddy doesn't have a stable package in Ubuntu yet, but it is fairly simple to install it by hand.

Installing Caddy

One of the first things you should do when installing Caddy is picking the list of extra plugins you want in addition to the core ones. I generally suggest the following plugins:

First we are going to need to download Caddy (please do this as root):

curl >
bash -s personal http.cors,http.git,http.supervisor
chown root:root /usr/local/bin/caddy
chmod 755 /usr/local/bin/caddy

These permissions are set as such:

Facet Read Write Directory Listing
User (root) Yes Yes Yes
Group (root) Yes No Yes
Others Yes No Yes

In order for Caddy to bind to the standard HTTP and HTTPS ports as non-root (this is a workaround for the fact that Go can't currently drop permissions with suid() cleanly), run the following:

setcap 'cap_net_bind_service=+eip' /usr/local/bin/caddy

Caddy expects configuration file/s to exist at /etc/caddy, so let's create the folders for them:

mkdir -p /etc/caddy
touch /etc/caddy/Caddyfile
chown -R root:www-data /etc/caddy

Let's Encrypt Certificate Permissions

Caddy's systemd unit expects to be able to create new certificates at /etc/ssl/caddy:

mkdir -p /etc/ssl/caddy
chown -R www-data:root /etc/ssl/caddy
chmod 770 /etc/ssl/caddy

These permissions are set as such:

Facet Read Write Directory Listing
User (www-data) Yes Yes Yes
Group (root) Yes Yes Yes
Others No No No

This will allow only Caddy and root to manage certificates in that folder.

Custom CA Certificate Permissions

In the last post, custom certificates were created at /srv/within/certs. Caddy is going to need to have the correct permissions in order to be able to read them.

chmod -R 750 .
chown -R root:www-data .
chmod 600 minica-key.pem

Then mark it executable:

chmod +x

These permissions are set as such:

Facet Read Write Execute/Directory Listing
User (root) Yes Yes Yes
Group (www-data) Yes No Yes
Others No No No

This will allow Caddy to be able to read the certificates later in the post. Run this after certificates are created.

cd /srv/within/certs

HTTP Root Permissions

I dypically store all of my websites under /srv/http/ To create a folder like this:

mkdir -p /srv/http
chown www-data:www-data /srv/http
chmod 755 /srv/http

These permissions are set as such:

Facet Read Write Directory Listing
User (www-data) Yes Yes Yes
Group (www-data) Yes No Yes
Others Yes No Yes


To install the upstream systemd unit, run the following:

curl -L \
      | sed "s/;CapabilityBoundingSet/CapabilityBoundingSet/" \
      | sed "s/;AmbientCapabilities/AmbientCapabilities/" \
      | sed "s/;NoNewPrivileges/NoNewPrivileges/" \
      | tee /etc/systemd/system/caddy.service
chown root:root /etc/systemd/system/caddy.service
chmod 744 /etc/systemd/system/caddy.service
systemctl daemon-reload
systemctl enable caddy.service

These permissions are set as such:

Facet Read Write Execute
User (root) Yes Yes Yes
Group (root) Yes No No
Others Yes No No

This will also configure Caddy to start on boot.

* Configure Caddy for static file serving for aloha.pele
    * root directive
    * browse directive
* Link to Caddy documentation

Configure aloha.pele

In the last post, we created the domain and TLS certificates for aloha.pele. Let's create a website for it.

Open /etc/caddy/Caddyfile and add the following:

# /etc/caddy/Caddyfile

aloha.pele:80 {
  tls off
  redir / https://aloha.pele:443

aloha.pele:443 {
  tls /srv/within/certs/aloha.pele/cert.pem /srv/within/certs/aloha.pele/key.pem
  internal /templates
  markdown / {
    template templates/page.html
  ext .md
  browse /
  root /srv/http/aloha.pele

And create /srv/http/aloha.pele/templates:

mkdir -p /srv/http/aloha.pele/templates
chown -R www-data:www-data /srv/http/aloha.pele/templates

And open /srv/http/aloha.pele/templates/page.html:

<!-- /srv/http/aloha.pele/templates/page.html -->

    <title>{{ .Doc.title }}</title>
      main {
        max-width: 38rem;
        padding: 2rem;
        margin: auto;
        <a href="/">Aloha</a>

      {{ .Doc.body }}

This will give a nice simple style kind of like this using Caddy's built-in markdown templating support. Now create /srv/http/aloha.pele/

<!-- /srv/http/aloha.pele/ -->

# Aloha!

This is an example page, but it doesn't have anything yet. If you see me, HTTPS is probably working.

Now let's enable and test it:

systemctl restart caddy
systemctl status caddy

If Caddy shows as running, then testing it via LibTerm should work:

curl -v https://aloha.pele

URL Shortener

I have created a simple URL shortener backend on my GitHub. I personally have it accessible at https://g.o for my internal network. It is very simple to configure:

Environment Variable Value
THEME solarized.css (or gruvbox.css)

surl requires a SQLite database to function. To store it, create a docker volume:

docker volume create surl

And to create the surl container and register it for automatic restarts:

docker run --name surl -dit -p \
  --restart=always \
  -e DOMAIN=g.o \
  -e THEME=solarized.css \
  -v surl:/data xena/surl:v0.4.0

Now create a DNS record for g.o.:


;; URL shortener
g.o. IN CNAME oho.pele.

And a TLS certificate:

cd /srv/within/certs
minica -domains g.o

And add Caddy configuration for it:

# /etc/caddy/Caddyfile

g.o:80 {
  tls off
  redir / https://g.o

g.o:443 {
  tls /srv/within/certs/g.o/cert.pem /srv/within/certs/g.o/key.pem
  proxy /

Now restart Caddy to load the configuration and make sure it works:

systemctl restart caddy
systemctl status caddy

And open https://g.o on your iOS device:

An image of the URL shortener in action

You can use the other directives in the Caddy documentation to do more elaborate things. When Then Zen is hosted completely with Caddy using the markdown directive; but even this is ultimately a simple configuration.

This seems like enough for this time. Next time we are going to approach adding other devices of yours to this network: iOS, Android, macOS and Linux.

Please give me feedback on my approach to this. I also have a Patreon and a Ko-Fi in case you want to support this series. I hope this is useful to you all in some way. Stay tuned for the future parts of this series as I build up the network infrastructure from scratch. If you would like to give feedback on the posts as they are written, please watch this page for new pull requests.

Be well. The sky is the limit, Creator!

This article was posted on M04 16 2019. Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.

Series: site-to-site-wireguard

This post was not WebMentioned yet. You could be the first!

The art for Mara was drawn by Selicre.

The art for Cadey was drawn by ArtZora Studios.

Some of the art for Aoi was drawn by @Sandra_Thomas01.