This is the multi-page printable view of this section. Click here to print.
Web
- 1: Access Logging
- 1.1: GoAccess
- 2: Content
- 2.1: Content Mgmt
- 2.1.1: Hugo
- 2.1.1.1: Hugo Install
- 2.1.1.2: Docsy Install
- 2.1.1.3: Docsy Config
- 2.1.1.4: Docsy Operate
- 2.1.1.5: Docsy Github
- 2.2: Content Deployment
- 2.2.1: Local Git Deployment
- 2.3: Content Delivery
- 2.3.1: Cloudflare
- 3: Servers
- 3.1: Caddy
- 3.1.1: Installation
- 3.1.2: Logging
- 3.1.3: WebDAV
- 3.1.4: MFA
- 3.1.5: Wildcard DNS
1 - Access Logging
1.1 - GoAccess
GoAccess is a lightweight web stats visualizer than can display in a terminal window or in a browser. It supports Caddy’s native JSON log format and can also be run as a real-time service with a little work.
Installation
If you have Debian/Ubuntu, you can add the repo as the [official docs] show.
# Note: The GoAccess docs use the `lsb_release -cs` utility that some Debains don't have, so I've substituted the $VERSION_CODENAME variable from the os-release file
wget -O - https://deb.goaccess.io/gnugpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/goaccess.gpg >/dev/null
source /etc/os-release
echo "deb [signed-by=/usr/share/keyrings/goaccess.gpg arch=$(dpkg --print-architecture)] https://deb.goaccess.io/ $VERSION_CODENAME main" | sudo tee /etc/apt/sources.list.d/goaccess.list
sudo apt update
sudo apt install goaccess
Basic Operation
No configuration is needed if your webserver is logging in a supported format1. Though you may need to adjust file permissions so the log file can be read by the user running GoAccess.
To use in the terminal, all you need to is invoke it with a couple parameters.
sudo goaccess --log-format CADDY --log-file /var/log/caddy/access.log
To produce a HTML report, just add an output file somewhere your web server can find it.
sudo touch /var/www/some.server.org/report.html
sudo chown $USER /var/www/some.server.org/report.html
goaccess --log-format=CADDY --output /var/www/some.server.org/report.html --log-file /var/log/caddy/access.log
Retaining History
History is useful and GoAccess lets you persist your data and incorporate it on the next run. This updates your report rather than replace it.
# Create a database location
sudo mkdir -p /var/lib/goaccess-db
sudo chown $USER /var/lib/goaccess-db
goaccess \
--persist --restore --db-path /var/lib/goaccess-db \
--log-format CADDY --output /var/www/some.server.org/report.html --log-file /var/log/caddy/access.log
GeoIP
To display country and city you need GeoIP databases, preferably the GeoIP2 format. An easy way to get started is with DB-IP’s Lite GeoIP2 databases that have a permissive license.
# These are for Jan 2025. Check https://db-ip.com/db/lite.php for the updated links
wget --directory-prefix /var/lib/goaccess-db \
https://download.db-ip.com/free/dbip-asn-lite-2025-01.mmdb.gz \
https://download.db-ip.com/free/dbip-city-lite-2025-01.mmdb.gz \
https://download.db-ip.com/free/dbip-country-lite-2025-01.mmdb.gz
gunzip /var/lib/goaccess-db/*.gz
goaccess \
--persist --restore --db-path /var/lib/goaccess-db \
--geoip-database /var/lib/goaccess-db/dbip-asn-lite-2025-01.mmdb \
--geoip-database /var/lib/goaccess-db/dbip-city-lite-2025-01.mmdb \
--geoip-database /var/lib/goaccess-db/dbip-country-lite-2025-01.mmdb \
--log-format CADDY --output /var/www/some.server.org/report.html --log-file /var/log/caddy/access.log
This will add a Country and ASN panel, and populate the city column on the “Visitor Hostnames and IPs” panel.
This one-time download is “good enough” for most purposes. But if you want to automate updates of the GeoIP data, you can create an account with a provider and get an API key. Maxmind offers free accounts. Sign up here and you can sudo apt install geoipupdate
to get regular updates.
Automation
A reasonable way to automate is with a logrotate hook. Most systems already have this in place to handle their logs so it’s an easy add. If you’re using Apache or nginx you probably already have one that you can just add a prerotate
hook to. For Caddy, something like this should be added.
sudo vi /etc/logrotate.d/caddy
/var/log/caddy/access.log {
daily
rotate 7
compress
missingok
prerotate
goaccess \
--persist --restore --db-path /var/lib/goaccess-db \
--geoip-database /var/lib/goaccess-db/dbip-asn-lite-2025-01.mmdb \
--geoip-database /var/lib/goaccess-db/dbip-city-lite-2025-01.mmdb \
--geoip-database /var/lib/goaccess-db/dbip-country-lite-2025-01.mmdb \
--log-format CADDY --output /var/www/some.server.org/report.html --log-file /var/log/caddy/access.log
endscript
postrotate
systemctl restart caddy
endscript
}
You can test this works with force option. If it works, you’ll be left with updated stats, an empty access.log
, and a newly minted access.log.1.gz
sudo logrotate --force /etc/logrotate.d/caddy
For Caddy, you’ll also need to disable it’s built-in log rotation
sudo vi /etc/caddy/Caddyfile
log {
output file /path/to/your/log.log {
roll_disabled
}
}
sudo systemctl restart caddy
Of course, this runs as root. You can design that out, but you can also configure systemd to trigger a timed execution and run as non-root. Arnaud Rebillout has some good info on that.
Real-Time Service
You can also run GoAccess as a service for real-time log analysis. It works in conjunction with your web server by providing a Web Socket to push the latest data to the browser.
In this example, Caddy is logging vhosts to individual files as described in the Caddy logging page. This is convenient as it allows you view vhosts separately which is often desired. Adjust as needed.
Prepare The System
# Create a system user with no home
sudo adduser --system --group --no-create-home goaccess
# Add the user to whatever group the logfiles are set to (ls -lah /var/log/caddy/ or whatever)
sudo adduser goaccess caddy
# Put the site in a variable to make edits easier
SITE=www.site.org
# Create a database location
sudo mkdir -p /var/lib/goaccess-db/$SITE
sudo chown goaccess:goaccess /var/lib/goaccess-db/$SITE
# Possibly create the report location
sudo touch /var/www/$SITE/report-$SITE.html
sudo chown goaccess /var/www/$SITE/report-$SITE.html
# Test that the goaccess user can create the report
sudo -u goaccess goaccess \
--log-format CADDY \
--output /var/www/$SITE/report-$SITE.html \
--persist \
--restore \
--db-path /var/lib/goaccess-db/$SITE \
--log-file /var/log/caddy/$SITE.log
Create The Service
sudo vi /etc/systemd/system/goaccess.service
[Unit]
Description=GoAccess Web log report
After=network.target
# Note: Variable braces are required for sysmtemd variable expansion
[Service]
Environment="SITE=www.site.org"
Type=simple
User=goaccess
Group=goaccess
Restart=always
ExecStart=/usr/bin/goaccess \
--log-file /var/log/caddy/${SITE}.log \
--log-format CADDY \
--persist \
--restore \
--db-path /var/lib/goaccess-db/${SITE} \
--output /var/www/${SITE}/report-${SITE}.html \
--real-time-html
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now goaccess.service
sudo systemctl status goaccess.service
sudo journalctl -u goaccess.service
If everything went well, it should be listening on the default port of 7890
nc -zv localhost 7890
localhost [127.0.0.1] 7890 (?) open
BUT you can’t access that port unless you’re on the same LAN. You can start forwarding that port and even setup SSL in the WS config, but in most cases it’s better to handle it with a proxy.
Configure Access via Proxy
To avoid adding additional port forwarding you can convert the websocket connection from a high-level port to a proxy path. This works with cloudflare as well.
Edit your GoAccess service unit to indicate the proxied URL.
ExecStart
...
# Note the added escape slash the the formerly last line
--real-time-html \
--ws-url wss://www.site.org:443/ws/goaccess
# If you don't add the port explicitly, GoAccess
# will 'helpfully' add the internal port (which isn't helpful), silently.
Add a proxy line to your web server. If using Caddy, add a path handler and proxy like this, and restart.
some.server.org {
...
...
handle_path /ws/goaccess* {
reverse_proxy * http://localhost:7890
}
}
Take your browser to the URL, and you should see the gear icon top left now has a green dot under it.
If the dot isn’t green you’re not connected so take a look at the troubleshooting section.
Create Multiple Services vs Multiple Reports
When you have lots of vhosts its useful to separate them at the log level and report separately. To do that you can use a systemd template so as to create multiple instances. Arnaud Rebillout has some details on that.
But scaling that becomes onerous. My preference is to automate report generation more frequently and skip the realtime.
Troubleshooting
No Data
Check the permissions on the files. If you accidentally typed caddy start
as root it will be running as root and later runs may not be able to write log entries.
GoAccess Isn’t Live
Your best bet is to open the developer tools in your browser, check the network tab and refresh. If proxying is wrong it will give you some hints.
What About The .conf
The file /etc/goaccess/goaccess.conf
can be used, just make sure to remove the arguments from the unit file so there isn’t a conflict.
Web Socket Intermittent
Some services, such as Cloudflare, support WS but can cause intermittent disconnections. Try separating your stats from your main traffic site.
-
See https://goaccess.io/man –log-format options ↩︎
2 - Content
2.1 - Content Mgmt
There are many ways to manage and produce web content. Traditionally, you’d use a large application with roles and permissions.
A more modern approach is to use a distributed version control system, like git, and a site generator.
Static Site Generators are gaining popularity as they produce static HTML with javascript and CSS that can be deployed to any Content Delivery Network without need for server-side processing.
Astro is great, as is Hugo, with the latter being around longer and having more resources.
2.1.1 - Hugo
Hugo is a Static Site Generator (SSG) that turns Markdown files into static web pages that can be deployed anywhere.
Like WordPress, you apply a ’theme’ to style your content. But rather than use a web-inteface to create content, you directly edit the content in markdown files. This lends itself well tomanaging the content as code and appeals to those who prefer editing text.
However, unlike other SSGs, you don’t have to be a front-end developer to get great results and you can jump in with a minimal investment of time.
2.1.1.1 - Hugo Install
Requirements
I use Debian in this example, but any apt-based distro will be similar.
Preparation
Enable and pin the Debian Backports and Testing repos so you can get recent versions of Hugo and needed tools.
Installation
Hugo requires git
and go
# Assuming you have enable backports as per above
sudo apt install -t bullseye-backports git
sudo apt install -t bullseye-backports golang-go
For a recent version of Hugo you’ll need to go to the testing repo. The extended version is recommended by Hugo and it’s chosen by default.
# This pulls in a number of other required packages, so take a close look at the messages for any conflicts. It's normally fine, though.
sudo apt install -t testing hugo
In some cases, you can just install from the Debian package with a lot less effort. Take a look at latest and copy the URL into a wget.
https://github.com/gohugoio/hugo/releases/latest
wget https://github.com/gohugoio/hugo/releases/download/v0.124.1/hugo_extended_0.124.1_linux-amd64.deb
Configuration
A quick test right from the quickstart page to make sure everything works
hugo new site quickstart
cd quickstart
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke
echo "theme = 'ananke'" >> config.toml
hugo server
Open up a browser to http://localhost:1313/
and you you’ll see the default ananke-themed site.
Next Steps
The ananke theme you just deployed is nice, but a much better theme is Docsy. Go give that a try.
Links
2.1.1.2 - Docsy Install
Docsy is a good-looking Hugo theme that provides a landing page, blog, and a documentation sub-sites using bootstrap CSS.
The documentation site in particular let’s you turn a directory of text files into a documentation tree with relative ease. It even has a collapsible left nav bar. That is harder to find than you’d think.
Preparation
Docsy requires Hugo. Install that if you haven’t already. It also needs a few other things; postcss, postcss-cli, and autoprefixer from the Node.JS ecosystem. These should be installed in the project directory as version requirements change per theme.
mkdir some.site.org
cd some.site.org
sudo apt install -t testing nodejs npm
npm install -D autoprefixer
npm install -D postcss
npm install -D postcss-cli
Installation
Deploy Docsy as a Hugo module and pull in the example site so we have a skeleton to work with. We’re using git, but we’ll keep it local for now.
git clone https://github.com/google/docsy-example.git .
hugo server
Browse to http://localhost:1313 and you should see the demo “Goldydocs” site.
Now you can proceed to configure Docsy!
Updating
The Docsy theme gets regular updates. To incorporate those you only have to run this command. Do this now, actually, to get any theme updates the example site hasn’t incoporated yet.
cd /path/to/my-existing-site
hugo mod get -u github.com/google/docsy
Troubleshooting
hugo
Error: Error building site: POSTCSS: failed to transform “scss/main.css” (text/css)>: Error: Loading PostCSS Plugin failed: Cannot find module ‘autoprefixer’
And then when you try to install the missing module
The following packages have unmet dependencies: nodejs : Conflicts: npm npm : Depends: node-cacache but it is not going to be installed
You may have already have installed Node.JS. Skip trying to install it from the OS’s repo and see if npm
works. Then proceed with postcss install and such.
2.1.1.3 - Docsy Config
Let’s change the basics of the site in the config.toml
file. I put some quick sed commands here, but you can edit by hand as well. Of note is the Github integration. We prepoulate it here for future use, as it allows quick edits in your browser down the road.
SITE=some.site.org
GITHUBID=someUserID
sed -i "s/Goldydocs/$SITE/" config.toml
sed -i "s/The Docsy Authors/$SITE/" config.toml
sed -i "s/example.com/$SITE/" config.toml
sed -i "s/example.org/$SITE/" config.toml
sed -i "s/google\/docsy-example/$GITHUBID\/$SITE/" config.toml
sed -i "s/USERNAME\/REPOSITORY/$GITHUBID\/$SITE/" config.toml
sed -i "s/https:\/\/policies.google.com//" config.toml
sed -i "s/https:\/\/github.com\/google\/docsy/https:\/\/github.com\/$GITHUBID/" config.toml
sed -i "s/github_branch/#github_branch/" config.toml
If you don’t plan to translate your site into different languages, you can dispense with some of the extra languages as well.
# Delete the 20 or so lines starting at "lLanguage] and stopping at the "[markup]" section,
# including the english section.
vi config.tml
# Delete the folders from 'content/' as well, leaving 'en'
rm -rf content/fa content/no
You should also set a default meta description or the engine will put in in the bootstrap default and google will summarize all your pages with that
vi config.toml
[params]
copyright = "some.site.org"
privacy_policy = "/privacy"
description = "My personal website to document what I know and how I did it"
Keep and eye on the site in your browser as you make changes. When you’re ready to start with the main part of adding content, take a look at the next section.
Notes
You can’t dispense with the en folder yet, as it breaks some github linking functionality you may want to take advantage of later
2.1.1.4 - Docsy Operate
This is a quick excerpt from the Docsy Content and Customization docs. Definitely spend time with those after reading the overview here.
Directory Layout
Content is, appropriately enough, in the content
directory, and it’s subdirectories line up with the top-level navigation bar of the web site. About, Documentation, etc corresponds to content/about
, content/docs
and so on.
The directories and files you create will be the URL that you get with one important exception, filenames are converted to a ‘slug’, mimicking how index files work. For example, If you create the file docs/tech/mastadon.md
the URL will be /docs/tech/mastadon/
. This is for SEO (Search Engine Optimization).
The other thing you’ll see are _index.html
files. In the example above, the URL /docs/tech/
has no content, as it’s a folder. But you can add a _index.md
or .html to give it some. Avoid creating index.md
or tech.md
(a file that matches the name of a subdirectory). Either of those will block Hugo from generating content for any subdirectories.
The Landing Page and Top Nav Pages
The landing page itself is the content/_index.html
file and the background is featured-background.jpg. The other top-nav pages are in the content folders with _index
files. You may notice the special header variable “menu: main: weight: " and that is what flags that specific page as worth of being in the top menu. Removing that, or adding that (and a linkTitle) will change the top nav.
The Documentation Page and Left Nav Bar
One of the most important features of the Docsy template is the well designed documentation section that features a Section menu, or left nav bar. This menu is built automatically from the files you put in the docs
folder, as long as you give them a title. (See Front Matter, below). They are ordered by date but you can add a weight to change that.
It doesn’t collapse by default and if you have a lot of files, you’ll want to enable that.
# Search and set in your config.toml
sidebar_menu_compact = true
Front Matter
The example files have a section at the top like this. It’s not strictly required, but you must have at least the title or they won’t show up in the left nav tree.
---
title: "Examples"
---
Page Content and Short Codes
In addition to normal markdown or html, you’ll see frequent use of ‘shortcodes’ that do things that normal markdown can’t. These are built in to Hugo and can be added by themes, and look like this;
{{% blocks/lead color="dark" %}}
Some Important Text
{{% /blocks/lead %}}
Diagrams
Docsy supports mermaid and a few other tools for creating illustrations from code, such as KaTeX, Mermaid, Diagrams.net, PlantUML, and MarkMap. Simply use a codeblock.
```mermaid
graph LR
one --> two
```
Generate the Website
Once you’re satisfied with what you’ve got, tell hugo to generate the static files and it will populate the folder we configured earlier
hugo
Publish the Web Site
Everything you need is in the public
folder and all you need do is copy it to a web server. You can even use git, which I advise since we’re already using it to pull in and update the module.
Bonus Points
If you have a large directory structure full of markdown files already, you can kick-start the process of adding frontmatter like this;
find . -type f | \
while read X
do
TITLE=$(basename ${X%.*})
FRONTMATTER=$(printf -- "---\ntitle = ${TITLE}\n---")
sed -i "1s/^/$FRONTMATTER\n/" "$X"
done
2.1.1.5 - Docsy Github
You may have noticed the links on the right like “Edit this page” that takes one to Github. Let’s set those up.
On Github
Go to github and create a new repository. Use the name of your side for the repo name, such as “some.site.org”. If you want to use something else, you can edit your config.toml
file to adjust.
Locally
You man have noticed that Github suggested some next steps with a remote add
using the name “origin”. Docsy is already using that, however, from when you cloned it. So we’ll have to pick a new name.
cd /path/to/my-existing-site
git remote add github https://github.com/yourID/some.site.org
Let’s change our default banch to “main” to match Github’s defaults.
git branch -m main
Now we can add, commit and push it up to Github
git add --all
git commit -m "first commit of new site"
git push github
You’ll notice something interesting when you go back to look at Github; all the contributers on the right. That’s because you’re dealing with a clone of Docsy and you can still pull in updates and changes from original project.
It may have been better to clone it via github
2.2 - Content Deployment
Automating deployment as part of a general continuous integration strategy is best-practice these days. Web content should be similarly treated.
I.e. version controlled and deployed with git.
2.2.1 - Local Git Deployment
Overview
Let’s create a two-tiered system that goes from dev to prod using a post-commit trigger
graph LR Development --git / rsync---> Production
The Development system is your workstation and the Production system is a web server you can rsync to. Git commit will trigger a build and rsync.
I use Hugo in this example, but any system that has an output (or build) folder works similarly.
Configuration
The first thing we need is a destination.
Production System
This server probably uses folders like /var/www/XXXXX
for its web root. Use that or create a new folder and make yourself the owner.
sudo mkdir /var/www/some.site.org
sudo chown -R $USER /var/www/some.site.org
echo "Hello" > /var/www/some.site.org/index.html
Edit your web server’s config to make sure you can view that web page. Also check that rsync
is available from the command line.
Development System
Hugo builds static html in a public
directory. To generate the HTML, simply type hugo
cd /path/to/my-existing-site
hugo
ls public
We don’t actually want this folder in git and most themes (if you’re using Hugo) already have a .gitignore
file. Take a look and create/add to it.
# Notice /public is at the top of the git ignore file
cat .gitignore
/public
package-lock.json
.hugo_build.lock
...
Assuming you have some content, let’s add and commit it.
git add --all
git commit -m "Initial Commit"
Note: All of these git commands work because pulling in a theme initialized the directory. If you’re doing something else you’ll need to git init
.
The last step is to create a hook that will build and deploy after a commit.
cd /path/to/my-existing-site
touch .git/hooks/post-commit
chmod +x .git/hooks/post-commit
vi .git/hooks/post-commit
#!/bin/sh
hugo --cleanDestinationDir
rsync --recursive --delete public/ [email protected]:/var/www/some.site.org
This script ensures that the remote directory matches your local directory. When you’re ready to update the remote site:
git add --all
git commit --allow-empty -m "trigger update"
If you mess up the production files, you can just call the hook manually.
cd /path/to/my-existing-site
touch .git/hooks/post-commit
Troubleshooting
bash: line 1: rsync: command not found
Double check that the remote host has rsync.
2.3 - Content Delivery
2.3.1 - Cloudflare
- Cloudflare acts as a reverse proxy to hide your server’s IP address
- Takes over your DNS and directs requests to the closest site
- Injects JavaScript analytics
- If the browser’s “do not track” is on, JS isn’t injected.
- Can uses a tunnel and remove encryption overhead
3 - Servers
3.1 - Caddy
Caddy is a web server that runs SSL by default by automatically grabing a cert from Let’s Encrypt. It comes as a stand-alone binary, written in Go, and makes a decent reverse proxy.
3.1.1 - Installation
Installation
Caddy recommends “using our official package for your distro” and for debian flavors they include the basic instructions you’d expect.
Configuration
The easiest way to configure Caddy is by editing the Caddyfile
sudo vi /etc/caddy/Caddyfile
sudo systemctl reload caddy.service
Sites
You define websites with a block that includes a root
and the file_server
directive. Once you reload, and assuming you already have the DNS in place, Caddy will reach out to Let’s Encrypt, acquire a certificate, and automatically forward from port 80 to 443
site.your.org {
root * /var/www/site.your.org
file_server
}
Authentication
You can add basic auth to a site by creating a hash and adding a directive to the site.
caddy hash-password
site.your.org {
root * /var/www/site.your.org
file_server
basic_auth {
allen SomeBigLongStringFromTheCaddyHashPasswordCommand
}
}
Reverse Proxy
Caddy also makes a decent reverse proxy.
site.your.org {
reverse_proxy * http://some.server.lan:8080
}
You can also take advantage of path-based reverse proxy. Note the rewrite to accommodate the trailing-slash potentially missing.
site.your.org {
rewrite /audiobooks /audiobooks/
handle_path /audiobooks/* {
uri strip_prefix /audiobooks/
reverse_proxy * http://some.server.lan:8080
}
}
Import
You can define common elements at the top (snippets) or in files and import them multiple times to save duplication. This helps when you have many sites.
# At the top in the global section of your Caddyfile
(logging) {
log {
output file /var/log/caddy/access.log
}
}
site.your.org {
import logging
reverse_proxy * http://some.server.lan:8080
}
Modules
Caddy is a single binary so when adding a new module (aka feature) you are essentially downloading a new version that has them compiled in. You can find the list of packages at their download page.
Do this at the command line with caddy itself.
sudo caddy add-package github.com/mholt/caddy-webdav
systemctl restart caddy
Security
Drop Unknown Domains
Caddy will accept connections to port 80, announce that it’s a Caddy web server and redirect you to https before realizing it doesn’t have a site or cert for you. Configure this directive at the bottom so it drops immediately.
http:// {
abort
}
Crowdsec
Caddy runs as it’s own user and is fairly memory-safe. But installing Crowdsec helps identify some types of intrusion attempts.
Troubleshooting
You can test your config file and look at the logs like so
caddy validate --config /etc/caddy/Caddyfile
journalctl --no-pager -u caddy
3.1.2 - Logging
Access Logs
In general, you should create a snippet and import into each block.
#
# Global Options Block
#
{
...
...
}
#
# Importable Snippets
#
(logging) {
log {
output file /var/log/caddy/access.log
}
}
#
# Host Blocks
#
site.your.org {
import logging
reverse_proxy * http://some.server.lan:8080
}
other.your.org {
import logging
reverse_proxy * http://other.server.lan:8080
}
Per VHost Files
If you want separate logs for separate vhosts, add a parameter to the import that changes the output file name.
#
# Global Options Block
#
{
...
...
}
#
# Importable Snippets
#
(logging) {
log {
# args[0] is appended at run time
output file /var/log/caddy/access-{args[0]}.log
}
}
#
# Host Blocks
#
site.your.org {
import logging site.your.org
reverse_proxy * http://some.server.lan:8080
}
other.your.org {
import logging other.your.org
reverse_proxy * http://other.server.lan:8080
}
Wildcard Sites
Wildcard sites only have one block so you must use the hostname
directive to separate vhost logs. This both sends a vhost to the file you want, and filters them out of others. You can also use an import argument as shown in this caddy example to save space. (I would never have deduced this on my own.)
#
# Global Options Block
#
{
...
...
}
#
# Importable Snippets
#
(logging) {
log {
output file /var/log/caddy/access.log {
roll_size 5MiB
roll_keep 5
}
}
}
(domain-logging) {
log {
hostnames {args[0]}
output file /var/log/caddy/{args[0]}.log {
roll_size 5MiB
roll_keep 5
}
}
}
#
# Main Block
#
*.site.org, site.org {
# Everything goes to this file unless it's filtered out by another log block
import logging
@site host some.site.org
handle @site {
reverse_proxy * http://internal.site
}
# the www site will write to just this log file
import domain-logging www.site.org
@www host www.site.org
handle @www {
root * /var/www/www.site.org
file_server
}
# This site will write to the normal file
@site host other.site.org
handle @site {
reverse_proxy * http://other.site
}
Logging Credentials
If you want to track users, add a directive to the global headers.
#
# Global Options Block
#
{
servers {
log_credentials
}
}
File Permissions
By default, only caddy can read the log files. This is a problem when you have a log analysis package. In recent versions of caddy however, you can set the mode.
log {
output file /var/log/caddy/access.log {
mode 644
}
}
If the log file doesn’t change modes, check the version of caddy. It must be newer than v2.8.4 for the change.
Troubleshooting
You can have a case where the domain specific file never gets created. This usually happens when there us nothing to write to it. Check the hostname
is correct.
3.1.3 - WebDAV
Caddy can also serve WebDAV requests with the appropriate module. This is important because for many clients, such as Kodi, WebDAV is significantly faster.
sudo caddy add-package github.com/mholt/caddy-webdav
sudo systemctl restart caddy
{ # Custom modules require order of precedence be defined
order webdav last
}
site.your.org {
root * /var/www/site.your.org
webdav *
}
You can combine WebDAV and Directly Listing - highly recommended - so you can browse the directory contents with a normal web browser as well. Since WebDAV doesn’t use the GET method, you can use the @get
filter to route those to the file_server module so it can serve up indexes via the browse
argument.
site.your.org {
@get method GET
root * /var/www/site.your.org
webdav *
file_server @get browse
}
Sources
https://github.com/mholt/caddy-webdav https://marko.euptera.com/posts/caddy-webdav.html
3.1.4 - MFA
The package caddy-security offers a suite of auth functions. Among these is MFA and a portal for end-user management of tokens.
Installation
# Install a version of caddy with the security module
sudo caddy add-package github.com/greenpau/caddy-security
sudo systemctl restart caddy
Configuration
/var/lib/caddy/.local/caddy/users.json
caddy hash-password
Troubleshooting
journalctl –no-pager -u caddy
3.1.5 - Wildcard DNS
Caddy has an individual cert for every virtual host you create. This is fine, but Let’s Encrypt publishes these as part of certificate transparency and the bad guys are watching. If you create a new site in caddy, you’ll see bots probing for weaknesses within 30 min - without you even having published the URL. There’s no security in anonymity, but the need-to-know principle suggests we shouldn’t be informing the whole world about sites of limited scope.
One solution is a wildcard cert. It’s published as just ‘*.some.org’ so there’s no information disclosed. Caddy supports this, but it requires a little extra work.
Installation
In this example we are already using the default Caddy binary but want to connect to CloudFlare’s DNS service. We must change to a custom Caddy binary for that. Check https://github.com/caddy-dns to see if your DNS provider is available.
# Divert the default binary from the repo
sudo dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy
sudo cp /usr/bin/caddy.default /usr/bin/caddy.custom
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50
# Add the package and restart.
sudo caddy add-package github.com/caddy-dns/cloudflare
sudo systemctl restart caddy.service
Warning:
Because we’ve diverted, apt update
will not update caddy. This also stops unattended-updates
. You must use caddy upgrade
instead. The devs don’t think this should be an issue. I disagree. but you can add a cron job if you like.
DNS Provider Configuration
For Cloudflare, a decent example is below. Just use the ‘Getting the Cloudflare API Token’ part
https://roelofjanelsinga.com/articles/using-caddy-ssl-with-cloudflare/
Caddy Configuration
Use the acme-dns
global option and then create a single site (used to determine the cert) and match the actual vhosts with subsites.
{
acme_dns cloudflare alotcharactersandnumbershere
}
*.some.org, some.org {
@site1 host site1.some.org
handle @site1 {
reverse_proxy * http://localhost:3200
}
@site2 host site2.some.org
handle @site2 {
root * /srv/www/site2
}
}