V2 Markdown, Templates, and HTTP error codes

1. Caddy version (caddy version):

v2.1.1 h1:X9k1+ehZPYYrSqBvf/ocUgdLSRIuiNiMo7CvyGUQKeA=

2. How I run Caddy:

I use Caddy v1 to power my personal website, using Markdown templates. I store raw Markdown files in my docroot, each with front matter, and have my Caddyfile apply a template to them. This has been great, because I can just drop a new file into the directory and Caddy servers it all styled for me.

I used to use Hugo, as someone else encouraged in the links, below, but site generation time started creeping up to 10+ seconds; and it’s fundamentally inefficient to regenerate the whole site when I add one new file. Caddy v1 rendering Markdown on the fly has been great for me!

a. System environment:

Ubuntu 20.04.1 LTS with Caddy (v1) running via systemd.

b. Command:

N/A

c. Service/unit/compose file:

[Unit]
Description=Caddy HTTP/2 web server
Documentation=https://caddyserver.com/docs
After=network-online.target
#Wants=network-online.target systemd-networkd-wait-online.service
Wants=network-online.target

[Service]
Restart=on-failure
# I've had to twiddle these values a few times
# when systemd reports that Caddy is restarting too fast
StartLimitInterval=600
StartLimitBurst=125

; User and group the process will run as.
User=www-data
Group=www-data

; Letsencrypt-issued certificates will be written to this directory.
Environment=CADDYPATH=/etc/caddy/ssl

; Always set "-root" to something safe in case it gets forgotten in the Caddyfile.
ExecStart=/usr/local/bin/caddy -log stdout -agree=true -email=skippy@skippy.net -conf=/etc/caddy/Caddyfile -root=/var/tmp -pidfile /var/run/caddy/caddy.pid
ExecReload=/bin/kill -USR1 $MAINPID

; Use graceful shutdown with a reasonable timeout
KillMode=mixed
KillSignal=SIGQUIT
TimeoutStopSec=5s

; Limit the number of file descriptors; see `man systemd.exec` for more limit settings.
LimitNOFILE=1048576
; Unmodified caddy is not expected to use more than that.
LimitNPROC=512

; Use private /tmp and /var/tmp, which are discarded after caddy stops.
PrivateTmp=true
; Use a minimal /dev
PrivateDevices=true
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
ProtectHome=true
; Make /usr, /boot, /etc and possibly some more folders read-only.
ProtectSystem=full
; … except /etc/caddy/ssl, because we want Letsencrypt-certificates there.
;   This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
ReadWriteDirectories=/etc/caddy/ssl

; The following additional security directives only work with systemd v229 or later.
; They further retrict privileges that can be gained by caddy. Uncomment if you like.
; Note that you may have to add capabilities required by any plugins in use.
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

d. My complete Caddyfile or JSON config:

https://skippy.net {
  root /var/www/skippy.net/
  gzip
  tls skippy@skippy.net
  ext .md
  markdown / {
    template home /templates/home.html
    template archive /templates/archive.html
    template /templates/single.html
  }
  log /var/log/caddy/skippy.log
  errors /var/log/caddy/skippy.log
}

3. The problem I’m having:

I’m looking to update my template files for use with the new v2 system. The old way made it easy to point to a Markdown file, and apply a template to that file.

Here are my home.html template, which serves index.md:

<!DOCTYPE html>
<html lang="en-us">
<head>
<style>
body{font:1.2em/1.62 sans-serif;margin:1em auto;max-width:40em;padding:0 .62em;}
h1,h2,h3{line-height:1.2;}
pre{background:#F8F8F8;overflow: auto;padding:0.5em;}
img{max-width:100%;}
img.u-photo{float:right;}
@media print{body{max-width:none}}
</style>
<title>skippy dot net</title>
<meta name="author" content="skippy">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="author" href="https://skippy.net/">
<link rel="alternate" type="application/rss+xml" href="https://skippy.net/index.xml" title="skippy.net" />
<link rel="feed" type="application/rss+xml" href="https://skippy.net/index.xml" title="skippy.net" />
<link rel="license" href="http://creativecommons.org/licenses/by/4.0/" />
<link rel="pgpkey" href="https://skippy.net/files/skippy.pub">
<link rel="me" href="https://micro.blog/skippy" />
<link rel="authorization_endpoint" href="https://micro.blog/indieauth/auth" />
<link rel="token_endpoint" href="https://micro.blog/indieauth/token" />
<link rel="microsub" href="https://aperture.p3k.io/microsub/38">
</head>
<body>
<article class="h-entry">
<header class="h-card">
<img class="u-photo" src="https://skippy.net/images/scott-merrill-2017.jpg" alt="Scott Merrill" />
<h1 class="p-name"><a rel="me" class="u-url" href="https://skippy.net/">Scott Merrill</a></h1>
<h2><a class="u-email" href="mailto:skippy@skippy.net">skippy@skippy.net</a></h2>
<h3>GPG key <a class="u-key" href="https://pgp.mit.edu/pks/lookup?op=get&search=0xD8F2E57BED0F2C0A">0xD8F2E57BED0F2C0A</a></h3>
<p><span class="p-locality">Columbus</span>, <span class="p-region">Ohio</span>, <span class="p-country-name">USA</span></p>
<p><a href="https://skippy.net/about">about me</a> / <a href="https://skippy.net/posts">posts</a></p>
<p><a rel="me" href="https://www.flickr.com/people/skippy">flickr</a> / <a rel="me" href="https://github.com/skpy">github</a> / <a rel="me" href="https://keybase.io/skippy">keybase</a> / <a rel="me" href="https://www.linkedin.com/in/scottdmerrill">linkedin</a></p>
<hr />
</header>
{{ .Doc.body }}
<hr />
<p><a rel="me" href="/">home</a> / <a href="/about">about</a> / <a rel="me" href="https://www.flickr.com/people/skippy">flickr</a> / <a rel="me" href="https://github.com/skpy">github</a> / <a rel="me" href="https://keybase.io/skippy">keybase</a> / <a rel="me" href="https://www.linkedin.com/in/scottdmerrill">linkedin</a></p>
</body>
</html>

And here is the current contents of index.md:

---
template: home
---
## Recent posts

* 2019-11-08 12:27:18 - [Bash Blog](https://skippy.net/bash-blog)
* 2019-10-22 12:00 - [Quitter](https://skippy.net/quitter)
* 2019-02-02 12:00 - [How To Meeting](https://skippy.net/how-to-meeting)
* 2019-01-03 13:59 - [Hugo Data Files](https://skippy.net/hugo-data-files)
* 2018-11-08 10:11 - [London 2018 - Day Six](https://skippy.net/london-2018---day-six)
* 2018-11-08 10:11 - [London 2018 - Day Seven](https://skippy.net/london-2018---day-seven)
* 2018-11-08 09:11 - [London 2018 - Day Five](https://skippy.net/london-2018---day-five)
* 2018-10-24 04:10 - [London 2018 - Day Two](https://skippy.net/london-2018---day-two)
* 2018-10-24 05:10 - [London 2018 - Day Three](https://skippy.net/london-2018---day-three)
* 2018-10-24 04:10 - [London 2018 - Day One](https://skippy.net/london-2018---day-one)

---
## Recent notes

[&para;](https://skippy.net/2020/05/10/1143 '2020-05-10 11:43:36')
Words to live by:

* wash your hands
* don’t drink bleach


![](https://skippy.net/images/2020/05/20200510-1143.jpg)

--

[&para;](https://skippy.net/2020/05/10/0908 '2020-05-10 09:07:53')
We’re doing Quarantine Bingo!

![](https://skippy.net/images/2020/05/20200510-197.jpg)

This is single.md, used for rendering a single post:

<!DOCTYPE html>
<html lang="en-us">
<head>
<style>
body{font:1.2em/1.62 sans-serif;margin:1em auto;max-width:40em;padding:0 .62em;}
h1,h2,h3{line-height:1.2;}
pre{background:#F8F8F8;overflow: auto;padding:0.5em;}
img{max-width:100%;}
img.u-photo{float:right;}
.separator{text-align:center;}
@media print{body{max-width:none}}
</style>
<title>skippy: {{ if .Doc.permalink -}}
{{- if eq (len (.Split .Doc.permalink "/")) 1 -}}{{ .Doc.title -}}
{{- else if .Doc.permalink }}{{ .Doc.permalink -}}
{{- end -}}
{{- else -}}
{{- .Doc.date -}}
{{- end -}}</title>
<meta name="author" content="skippy">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="author" href="https://skippy.net/">
<link rel="alternate" type="application/rss+xml" href="https://skippy.net/index.xml" title="skippy.net" />
<link rel="feed" type="application/rss+xml" href="https://skippy.net/index.xml" title="skippy.net" />
<link rel="license" href="http://creativecommons.org/licenses/by/4.0/" />
<link rel="pgpkey" href="https://skippy.net/files/skippy.pub">
</head>
<body>
<article class="h-entry">
{{- if .Doc.permalink }}
{{ if eq (len (.Split .Doc.permalink "/")) 1 }}
<h1 class="p-name"><a class="u-url" href="/{{ .Doc.permalink }}" title="{{ .Doc.title }}">{{ .Doc.title }}</a></h1>
{{- end }}{{- end }}
<div class="e-content">
{{ .Doc.body }}
</div>
{{ if .Doc.permalink }}
<h4>published <time class="dt-published" datetime="{{ .Doc.date }}"><a href="/{{ .Doc.permalink }}">{{ .Doc.date }}</a></time><a class="p-author h-card" href="https://skippy.net/" title="skippy"></a></h4>
{{ end }}
</article>
<hr />
<p><a rel="me" href="/">home</a> / <a href="/about">about</a> / <a rel="me" href="https://www.flickr.com/people/skippy">flickr</a> / <a rel="me" href="https://github.com/skpy">github</a> / <a rel="me" href="https://keybase.io/skippy">keybase</a> / <a rel="me" href="https://www.linkedin.com/in/scottdmerrill">linkedin</a></p>
</body>
</html>

And here’s a part of what one entry looks like:

---
title: Quitter
permalink: quitter
date: 2019-10-22 12:00
---
I celebrated my twelfth anniversary of using Twitter by [deleting almost all of my tweets](https://twitter.com/smerrill/status/1186256887773388806). I left that one, plus my [Keybase verification](https://twitter.com/smerrill/status/446651749474902016) so that my Keybase proof wouldn’t break. I’m not sure I care about that, after all, so I may soon simply terminate my entire Twitter account.

To recap: my current workflow is that Caddy gets a request, it applies .md to that request via the ext directive, and reads the associated Markdown file into memory. Then it reads the template from the front matter of that Markdown and loads up the corresponding HTML template. It synthesizes that template and the Markdown contents and serves the result back to the requestor.

If a request is made for a non-existent file, Caddy properly serves an HTTP 404 error.

If I’m reading the v2 information (see links, below) correctly, this model is inverted in the new format: I point requests to a template, and that template is responsible for parsing the URL and fetching a Markdown file for inclusion.

I can live with re-arranging all of my content to use this new form, but it seems like this doesn’t handle error conditions very well. The template doesn’t seem to provide a way to respond with an HTTP 404 in the event that the to-be-included file doesn’t exist?

Indeed, the Caddy docs themselves simply terminate the connection without an HTTP code at all if you request an invalid URL:

❯ curl -v https://caddyserver.com/docs/foo
*   Trying 165.227.20.207...
* TCP_NODELAY set
* Connected to caddyserver.com (165.227.20.207) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=caddyserver.com
*  start date: Sep 15 18:51:51 2020 GMT
*  expire date: Dec 14 18:51:51 2020 GMT
*  subjectAltName: host "caddyserver.com" matched cert's "caddyserver.com"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fb664808600)
> GET /docs/foo HTTP/2
> Host: caddyserver.com
> User-Agent: curl/7.54.0
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* HTTP/2 stream 1 was closed cleanly, but before getting  all response header fields, teated as error
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):
curl: (92) HTTP/2 stream 1 was closed cleanly, but before getting  all response header fields, teated as error

Is there an easy and sane way to provide 404 messages for errors when using templates to serve Markdown files?

Before I go whole-hog on rearranging all of my content and templates, might there be a lower-friction way to recreate my current setup with Caddy v2?

Finally, is Caddy v1 officially end-of-life? At what point should I expect Caddy v1 to stop working with Let’s Encrypt, for example? (I assume at some point in the future LE will update in some way that doesn’t get support added back to Caddy v1, which renders Caddy v1 pretty much dead in the water at the point.)

4. Error messages and/or full log output:

N/A

5. What I already tried:

N/A

6. Links to relevant resources:

Almost. We set the date for Oct 1st 2020: Security Policy · caddyserver/caddy · GitHub

That’s totally dependent on Let’s Encrypt and whether they make radical changes, enough to warrant turning off the current API versions, which doesn’t seem that likely right now. But Caddy v2 has much improved certificate management, and many of those improvements aren’t in v1 (better handling to avoid hitting rate limits, among others).

I’m not sure there’s much in the templating system specifically for this right now.

What you could do though, is at the Caddyfile level, use the file matcher to check whether a relevant markdown file exists, and if so, return a 404 there.

I don’t know what your site structure looks like, but maybe something like this:

example.com

root * /srv
templates
file_server

@missingFile not file {path} {path}.md
respond @missingFile "Not found" 404

This topic was automatically closed after 30 days. New replies are no longer allowed.