1. The problem I’m having:
I am setting up a server for several virtual hosts, each of which is supposed to have its own access log.
Caddy fails to start, because it can’t create one of the parent directories of one of the log files. But it shouldn’t be trying to create that directory at all; it already exists!
2. Error messages and/or full log output:
Sep 23 22:42:09 tinka systemd[1]: Starting Caddy...
Sep 23 22:42:09 tinka caddy[749]: {"level":"info","ts":1758667329.4293368,"msg":"maxprocs: Leaving GOMAXPROCS=2: CPU quota undefined"}
Sep 23 22:42:09 tinka caddy[749]: {"level":"info","ts":1758667329.4297462,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":7493245747,"previous":9223372036854775807}
Sep 23 22:42:09 tinka caddy[749]: {"level":"info","ts":1758667329.4298112,"msg":"using config from file","file":"/etc/caddy/caddy_config"}
Sep 23 22:42:09 tinka caddy[749]: {"level":"info","ts":1758667329.432147,"msg":"adapted config to JSON","adapter":"caddyfile"}
Sep 23 22:42:09 tinka caddy[749]: {"level":"info","ts":1758667329.4343011,"msg":"maxprocs: No GOMAXPROCS change to reset"}
Sep 23 22:42:09 tinka caddy[749]: Error: loading initial config: loading new config: setting up custom log 'log2': opening log writer using &logging.FileWriter{Filename:"/home/web-research/logs/access.log", Mode:0x0, Roll:(*bool)(nil), RollSizeMB:0, RollCompress:(*bool)(nil), RollLocalTime:false, RollKeep:0, RollKeepDays:0}: mkdir /home/web-research: permission denied
Sep 23 22:42:09 tinka systemd[1]: caddy.service: Main process exited, code=exited, status=1/FAILURE
Sep 23 22:42:09 tinka systemd[1]: caddy.service: Failed with result 'exit-code'.
Sep 23 22:42:09 tinka systemd[1]: Failed to start Caddy.
Demonstrating that the directory it’s trying to create already exists:
# ls -ld / /home /home/web-research /home/web-research/logs
drwxr-xr-x 16 root root 320 Sep 23 22:30 /
drwxr-xr-x 6 root root 4096 Sep 23 22:29 /home
drwx--x--x 4 web-research users 4096 Sep 23 22:32 /home/web-research
drwxrwxr-x 2 web-research caddy 4096 Sep 23 22:41 /home/web-research/logs
Demonstrating that the caddy daemon account can write to that directory already:
# ls -l /home/web-research/logs
total 0
# runuser -u caddy -- touch /home/web-research/logs/access.log
# ls -l /home/web-research/logs/
total 0
-rw-r--r-- 1 caddy caddy 0 Sep 23 22:54 access.log
3. Caddy version:
2.10.0
4. How I installed and ran Caddy:
Installed as NixOS 25.05 system service. Relevant excerpt of configuration.nix:
{
services.caddy = {
enable = true;
email = "[REDACTED]";
globalConfig = ''
default_sni [REDACTED]
'';
extraConfig = ''
'';
virtualHosts = {
# ...
};
};
}
Caddy is then started on boot as a systemd service.
a. System environment:
# uname -a
Linux tinka 6.12.48 #1-NixOS SMP PREEMPT_DYNAMIC Fri Sep 19 14:35:52 UTC 2025 x86_64 GNU/Linux
# nixos-version
25.05.20250923.d1d8831 (Warbler)
# systemctl --version
systemd 257 (257.9)
+PAM +AUDIT -SELINUX +APPARMOR +IMA +IPE +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -BTF -XKBCOMMON +UTMP -SYSVINIT +LIBARCHIVE
Please let me know if anything else is relevant.
b. Command:
systemctl start caddy.service
c. Service/unit/compose file:
This is 100% NixOS stock. The substantial amount of gobbledygook in here is just how NixOS works.
# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
StartLimitBurst=10
StartLimitIntervalSec=14400
X-Reload-Triggers=/nix/store/vhsm0pvjsdj3zzm725jkdra2zl9pdx44-X-Reload-Triggers-caddy
[Service]
Type=notify
User=caddy
Group=caddy
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LogsDirectory=caddy
StateDirectory=caddy
NoNewPrivileges=true
PrivateDevices=true
ProtectHome=true
ReadWritePaths=/var/lib/caddy
Restart=on-failure
RestartPreventExitStatus=1
RestartSec=5s
Environment="LOCALE_ARCHIVE=/nix/store/yczfclil9jjhcbdy1a6rqinihmfdzcz3-glibc-locales-2.40-66/lib/locale/locale-archive"
Environment="PATH=/nix/store/rry6qingvsrqmc7ll7jgaqpybcbdgf5v-coreutils-9.7/bin:/nix/store/392hs9nhm6wfw4imjllbvb1wil1n39qx-findutils-4.10.0/bin:/nix/store/l2wvwyg680h0v2la18hz3yiznxy2naqw-gnugrep-3.11/bin:/nix/store/4rpiqv9yr2pw5094v4wc33ijkqjpm9sa-gnused-4.9/bin:/nix/store/d84f8nm2na5cr53m4jk0qk2mj7lgr9fx-systemd-257.9/bin:/nix/store/rry6qingvsrqmc7ll7jgaqpybcbdgf5v-coreutils-9.7/sbin:/nix/store/392hs9nhm6wfw4imjllbvb1wil1n39qx-findutils-4.10.0/sbin:/nix/store/l2wvwyg680h0v2la18hz3yiznxy2naqw-gnugrep-3.11/sbin:/nix/store/4rpiqv9yr2pw5094v4wc33ijkqjpm9sa-gnused-4.9/sbin:/nix/store/d84f8nm2na5cr53m4jk0qk2mj7lgr9fx-systemd-257.9/sbin"
Environment="TZDIR=/nix/store/f7yb9lhi1z8dk4x8gy3c5xf3gvn3yi1s-tzdata-2025b/share/zoneinfo"
ExecReload=/nix/store/z26qnmzvrv93x3fprcnvc0s7ja131lsr-caddy-2.10.0/bin/caddy reload --config /etc/caddy/caddy_config --adapter caddyfile --force
ExecStart=/nix/store/z26qnmzvrv93x3fprcnvc0s7ja131lsr-caddy-2.10.0/bin/caddy run --config /etc/caddy/caddy_config --adapter caddyfile
[Install]
WantedBy=multi-user.target
d. My complete Caddy config:
I did cut this down substantially, but then I rechecked that the cut-down version produces the exact same error.
{
default_sni tinka.owlfolio.org
# test mode for test VM
debug
local_certs
skip_install_trust
email not.a.real.email@dont.use.invalid
}
research.owlfolio.org {
log {
output file /home/web-research/logs/access.log
}
root * /home/web-research/html
file_server {
precompressed
}
encode
}
# three other vhosts redacted