---
slug: "site-backend"
title: "Site Backend"

# date (optional for articles)
date: "2024-05-31T03:57:18-04:00"

# show on articles page
show: true

# uncomment to show table of contents
toc: true

pics:
  editing:
    css: "image"
    tip: "Editing this article with Vim and Firefox."
    sources:
      - "/files/articles/site-backend/0-editing-1024.webp"
      - src: "/files/articles/site-backend/0-editing-1024.png"
        width: 1024
        height: 640
  ssl-labs-report:
    css: "image"
    tip: "SSL Labs SSL Test results for this site."
    sources:
      - "/files/articles/site-backend/1-ssl-labs-20240530-1024.webp"
      - src: "/files/articles/site-backend/1-ssl-labs-20240530-1024.png"
        width: 1024
        height: 525
  securityheaders-report:
    css: "image"
    tip: "Security headers report for this site from securityheaders.com"
    sources:
      - "/files/articles/site-backend/2-securityheaders-1024.webp"
      - src: "/files/articles/site-backend/2-securityheaders-1024.png"
        width: 1024
        height: 320
---
## Overview

Site content is managed as [Markdown][] files stored in a [Git][]
repository.  The theme is a modified version of [Bulma][].  The site is
statically generated by [Hugo][].

I create and edit site content with [Vim][] and preview the results
locally with `hugo serve -D ...`.  Articles and blog posts are saved as
drafts and committed to the [Git][] repository so I can work on them as
time permits.

[{{< pe-figure "editing" >}}][editing-this-article]

## History

From 1998 to 2019 this site was generated by a custom [PHP][] backend,
written by me.  In 2019 I resurrected the site after a several year
hiatus and migrated the site from the custom [PHP][] backend to
[Jekyll][].  In 2021 I switched from [Jekyll][] to [Hugo][].

## Goals

The content and layout of this site should be:

- Small
- Fast
- Secure
- Accessible
- Mobile-friendly

These goals are addressed as follows:

- Small: [Hugo][] is configured to aggressively pack content and minify
  assets.  Images are compressed and served in multiple formats.  [HTTP
  compression][http compression] is enabled (with caution).  See
  [Hugo Configuration][s-hugo-configuration], [Images][s-images] and
  [Apache Configuration][s-apache-configuration].
- Fast: Static content can be served quickly.  Assets are fingerprinted
  and browser caching is enabled.  See [Hugo
  Configuration][s-hugo-configuration] and [Apache
  Configuration][s-apache-configuration].
- Secure: Static content; no web-accessible endpoint which can make
  changes.  Additional security measures are discussed in
  [Deployment][s-deployment], [Apache
  Configuration][s-apache-configuration], and [Other][s-other].
- Accessible: Addressed when creating content and with custom
  [shortcodes][shortcode].  See [HTML][s-html] and [Hugo
  Configuration][s-hugo-configuration].
- Mobile-friendly: Addressed when creating content and with the site
  theme.  See [HTML][s-html] and [Bulma
  Configuration][s-bulma-configuration].

## Content

This section describes how content is created for this site.

### HTML

The site content and layout is designed to be accessible and
mobile-friendly:

- links and table components have `title` and `aria-label` attributes
- images have captions, fallback formats, and `title` and `alt` attributes
- images scale gracefully to thumbnails on mobile
- supports light and [dark mode][] and has a [manual theme
  switcher][post-theme-switcher]
- The menu bar collapes to a hamburger menu on mobile.

Here are a few articles which cover guidelines that I follow:

- [Progressive Enhancement][]
- [Why your website should be under 14kB in size][14kb]
- [5 things you don't need JavaScript for][you-dont-need-js]



### Images

Images are created as follows:

- Mathematical content is created using [Mathy][], saved as [SVG][],
  minified using [minify][], and served cached and compressed by [Apache][].
- Charts are created with [Matplotlib][], saved as [SVG][], minified
  with [minify][], and served cached and compressed by [Apache][]
  Example: [This Python script][bench-chart] was used to generate the
  bar charts in [this blog post][post-fips203ipd].
- Lossless raster images (e.g. screenshots) are scaled and cropped with
  [GraphicsMagick][] and served as a lossless [WebP][] with a [PNG][]
  fallback compressed by [pngquant][].
- Lossy raster images (e.g. pictures) are scaled and cropped with
  [GraphicsMagick][] and served as a lossy [WebP][] with a [JPEG][]
  fallback.

Other notes:

- The animated site logo is an [SVG][] generated by [this Ruby script][gen-logo.rb].
- Menubar icons are borrowed from [Bootstrap Icons][].
- I reviewed several [PNG][] compressors in [this post][post-png-compressors].

Managing images manually helps to keep the site small and fast.  Here
are a couple common [GraphicsMagick][] commands:

```sh
# scale with antialiasing and convert from png to webp
gm convert -antialias -scale 1024x1024 some-image.{png,webp}

# get image dimensions
gm identify some-image.png

# convert from png to webp (lossless)
gm convert -quality 100 -define webp:lossless=true some-image.{png,webp}

# crop image to 256x256
gm convert -crop 256x256 some-image{,-cropped}.png
```

### JavaScript

This site has almost no [JavaScript][], by design.  The [JavaScript][]
that is used is minimal, [modern][es2015], [deferred][], and only used
for the [theme switcher][post-theme-switcher] and burger menu.

Below is the unminified `script.js` for this site with some notes
removed.  It is served as 777 bytes minified and 586 bytes minified and
compressed:

```js
'use strict';

//
// script.js - script which handles:
//
// - set theme
// - theme switcher and burger menu event handlers
//

const D = document,
      C = D.body.parentElement.classList,
      L = localStorage,
      M = window.matchMedia,
      on = (el, id, fn) => el.addEventListener(id, fn);

// use theme if set, otherwise fall back dark mode
C.add('dark');
if (L && L.theme && L.theme === 'light') {
  C.remove('dark'); // set light theme
}

document.addEventListener('DOMContentLoaded', () => {
  // theme toggle event handler
  on(D.querySelector('.navbar-item[data-id="theme"]'), 'click', (e) => {
    e.preventDefault(); // stop event
    L.theme = C.toggle('dark') ? 'dark' : 'light'; // toggle
  });

  // iterate through burgers, bind to click events
  D.querySelectorAll('.navbar-burger').forEach(e => on(e, 'click', () => {
    // then toggle is-active on burger and menu
    [e, D.getElementById(e.dataset.target)].forEach(
      e => e.classList.toggle('is-active')
    )
  }));
});
```
[Download][script.js]

## Deployment

Once I am ready to apply any outstanding changes to the public web site,
I push the outstanding commits to a remote [Git][] repository. This
triggers a [`post-receive` Git hook][post-receive], which sends an
[HMAC][]-authenticated `POST` request to a [web hook][] endpoint on the
web server.

The [web hook][] verifies the [HMAC][] and then runs [a deployment
script][deploy.rb] which does the following:

1. Verifies the authenticated timestamp (to prevent [replay attacks][]).
2. Clones the upstream repository.
3. Executes [Hugo][] (`hugo --minify -d ...`) to build the site in an
   isolated output directory.
4. Updates the `htdocs` symlink for the public-facing web site to point
   at the output directory from the previous step.
5. Removes any stale builds.

## Configuration

This section discusses the configuration for [Apache][], [Bulma][],
[Hugo][], and [Webhook][].

### Apache Configuration

This section disusses the [Apache][] configuration for this site.  The
information is divided into several sub-sections in order to make it
easier to digest.

This site relies on the following [Apache][] modules:

- [mod\_deflate][mod-deflate]: Enable [HTTP compression][].
- [mod\_http2][mod-http2]: Enable [HTTP/2][].
- [mod\_macro][mod-macro]: Simplify common configuration.
- [mod\_proxy][mod-proxy]: Proxy [web hook][] requests to internal
  [webhook][] daemon.
- [mod\_rewrite][mod-rewrite]: Unconditionally redirect from [HTTP][] to
  [HTTPS][], strip `www.` from the path hostname, and redirect from legacy
  [URLs][url].

#### Virtual Host Configuration

The [Apache][] virtual host configuration is modified as follows:

- Unconditionally redirect from [HTTP][] to [HTTPS][]
- Unconditionally redirect to strip the `www.` hostname prefix
- Enable [HTTP/2][]
- Set security headers (discussed in [Security
  Headers][s-security-headers])
- Enable aggressive caching of image, script, and style assets

Below is the [Apache][] virtual host configuration for this site with
extraneous comments and configuration for logging, [TLS][], and legacy
redirects removed:

```apache
# unconditionally redirect to https://pablotron.org
<VirtualHost *:80>
  RewriteEngine On
  RewriteRule ^/(.*)$ https://pablotron.org/$1 [R,L]
</VirtualHost>

<VirtualHost *:443>
  # strip "www." prefix and enable mod_deflate
  Use STRIP_WWW https://pablotron.org
  Use MOD_DEFLATE

  # enable http2
  Protocols h2 http/1.1

  # set restrictive content security policy
  Header append "Content-Security-Policy" "default-src 'self'; img-src 'self' https://pmdn.org"

  # set remaining security headers
  Header append "Strict-Transport-Security" "max-age=31536000"
  Header append "X-Frame-Options" "SAMEORIGIN"
  Header append "X-Content-Type-Options" "nosniff"
  Header append "Cross-Origin-Opener-Policy" "same-origin"
  Header append "Cross-Origin-Resource-Policy" "same-origin"
  Header append "Access-Control-Allow-Origin" "https://pablotron.org"
  Header append "Referrer-Policy" "strict-origin-when-cross-origin"

  # set permissions policy
  Header append "Permissions-Policy" "camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), usb=()"

  # POST needed for /hooks
  Header append "Access-Control-Allow-Methods" "POST, GET, HEAD, OPTIONS"

  # cache images, stylesheets, and javascript for 1 year
  <FilesMatch "\.(ico|jpg|jpeg|png|gif|webp|svg|js|json|css)$">
    Header set Cache-Control "max-age=31536000, public"
  </FilesMatch>

  # allow style-src-attr unsafe-inline for svgs
  # (without this svgs do not render in firefox)
  <FilesMatch "\.svg$">
    Header set "Content-Security-Policy" "default-src 'self'; img-src 'self'; style-src-attr 'self' 'unsafe-inline'"
  </FilesMatch>

  # expose webhook
  <Location /hooks/>
    ProxyPass "http://localhost:9000/"
    ProxyPassReverse "http://localhost:9000/"
  </Location>
</VirtualHost>
```

[Download][apache-vhost.conf]

#### HTTP Compression

[HTTP compression][] is supported via [mod\_deflate][mod-deflate].

It is safe for this site to enable [mod\_deflate][mod-deflate] because
it does not use [cookies][] and is not vulnerable to [BREACH][].

In 2022 I tried [mod\_brotli][mod-brotli], but the improvement over
[mod\_deflate][mod-deflate] was minimal (deflate: 125k, brotli: 117k) so
I abandoned it.

#### Security Headers

This site uses a strict [Content-Security-Policy][] header; it rejects
links to all external assets with one exception: references to images
hosted on <https://pmdn.org/> are allowed.

The trickiest part of restricting [Content-Security-Policy][] was
`style-src`; many content generation tools (including the [Markdown][]
table generator in [Hugo][]) break without `style-src 'unsafe-inline'`
(e.g., the ability to emit arbitrary `style` attributes).

In order to work around the `style-src` issues I ended up [reconfiguring
the Hugo syntax highlighter](#hugo-configuration) and writing [a custom
Hugo table shortcode][hugo-shortcode-table] which only emits `class`
attributes for inline styling.

The journey to a strict [Content-Security-Policy][] is documented in
this series of blog posts from October 2021:

- [Hugo/Content-Security-Policy Impedance Mismatch (October 19, 2021)][post-headers-1]
- [TLS and Header Fixes (October 21, 2021)][post-headers-2]
- [The Nuclear Option (No More unsafe-inline) (October 25, 2021)][post-headers-3]

The remaining security headers are explained in the following articles:

- [Security headers quick reference][security-headers-quick-ref]
- [Goodby Feature Policy and hello Permissions Policy!][permissions-policy]
- [Permissions Policy Explainer][]
- [Referrer-Policy][]

The security headers on this site earn an A+ rating from
[securityheaders.com][]:

[{{< pe-figure "securityheaders-report" >}}][securityheaders-report]

#### TLS Configuration

The certificate for this site is issued by [Let's Encrypt][] and managed
automatically by [certbot][] with the [certbot-dns-linode][] plugin.

The [Apache][] [TLS][] configuration is based on the intermediate
settings from the [Mozilla SSL Configuration Generator][ssl-config].  In
particular, only [TLS 1.2][] and [TLS 1.3][] are enabled, and the
insecure ciphers from [TLS 1.2][] have been disabled.

```apache
# explicit list of cipher suites (from ssl-config.mozilla.org)
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

# use server priorities for cipher algorithm choice
SSLHonorCipherOrder on

# protocols to enable (only TLS 1.2 and TLS 1.3)
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
```

[Download][apache-tls.conf]

This [TLS][] configuration earns an A+ result from the [SSL Labs SSL
Test][ssl-labs-ssl-test]:

[{{< pe-figure "ssl-labs-report" >}}][ssl-labs-report]

### Bulma Configuration

The site [CSS][] based on the [Bulma][] with the following
modifications:

1. All unused components removed
2. monokai style for [Chroma][] added
3. Styles for navbar icon highlighting and table captions added
4. dark mode styles added

Here is the [SASS][]:

```sass
// style.sass: based on bulma-0.9.3/sass/bulma.sass with the following
// changes:
//
// 1. all unused components removed
// 2. monokai style for chroma added
// 3. styles for navbar icon highlighting and table captions added
// 4. dark mode styles added
@charset "utf-8"

// import chroma style
//
// generated with the following command:
//
//   cd themes/hugo-pt2021/assets
//   hugo gen chromaclasses --style=monokai > chroma.css
//
@import "chroma"

@import "bulma-0.9.3/sass/utilities/_all"
@import "bulma-0.9.3/sass/base/_all"

// elements
@import "bulma-0.9.3/sass/elements/button"
@import "bulma-0.9.3/sass/elements/container"
@import "bulma-0.9.3/sass/elements/content"
@import "bulma-0.9.3/sass/elements/image"
@import "bulma-0.9.3/sass/elements/table"
@import "bulma-0.9.3/sass/elements/title"
@import "bulma-0.9.3/sass/elements/other"

// components
@import "bulma-0.9.3/sass/components/media"
@import "bulma-0.9.3/sass/components/navbar"

// grid (reenabled, used for images)
@import "bulma-0.9.3/sass/grid/_all"

// helpers
@import "bulma-0.9.3/sass/helpers/_all"

// layout
@import "bulma-0.9.3/sass/layout/section"
@import "bulma-0.9.3/sass/layout/footer"

// dim navbar icons by default
.navbar-item .menu-icon
  opacity: 60%

// highlight icons on hover
.navbar-item:hover .menu-icon
  opacity: 100%

// table captions below tables
table.table
  caption-side: bottom

// dark mode (2024-05-27)
@import "dark"
```
[Download][style.sass.txt]

With these changes the generated [CSS][] is 137 kB minified and 16 kB
minified and compressed.

### Hugo Configuration

[Hugo][] is configured to use [Chroma syntax higlighting][chroma] with
inline styles disabled in order to support the strict
[Content-Security-Policy][]. See [Security Headers][s-security-headers]
for details.

Tables are are generated by [a custom table
shortcode][hugo-shortcode-table], because the [Hugo's][hugo] native
[Markdown][] table generator in [Hugo][] uses inline styles.

I have also written a couple of custom [shortcodes][shortcode] to
generate `<picture>` and `<figure>` elements in order to support
[progressive enhancement][].

Custom archetypes have been added for the [Archives][] section, blog
posts, articles, and projects.

The generated [HTML][] has been modified to:

- Use a custom theme.  See [Bulma Configuration][s-bulma-configuration].
- Add support for [`<meta name='go-import' ...>`][go-import].
- Add a [Mastodon][] `<link rel='me' ...>` tag.
- Remove all unnecessary tags.
- Combine, [minify][], and enable caching of [JavaScript][] and [CSS][] assets.
- add `integrity` attributes to `<link>` and `<script>` tags.

### Webhook Configuration

[webhook][] configuration with [HMAC][] key removed:

```json
[{
  "id": "deploy-pablotron-org",
  "execute-command": "/data/www/pablotron.org/git/bin/hook/deploy.rb",

  "pass-arguments-to-command": [{
    "source": "payload",
    "name": "time"
  }],

  "pass-environment-to-command": [{
    "source": "string",
    "envname": "DEPLOY_HTDOCS_PATH",
    "name": "/data/www/pablotron.org/builds/current"
  }, {
    "source": "string",
    "envname": "DEPLOY_REPO_DIR",
    "name": "/data/www/pablotron.org/git"
  }, {
    "source": "string",
    "envname": "DEPLOY_BUILDS_DIR",
    "name": "/data/www/pablotron.org/builds"
  }],

  "trigger-rule": {
    "match": {
      "type": "payload-hmac-sha256",
      "secret": "(omitted)",
      "parameter": {
        "source": "header",
        "name": "X-Hub-Signature"
      }
    }
  }
}]
```

[Download][webhook.conf]

## Validation

I periodically use the following tools to verify this site:

- Developer Console: Check page load time, cached and uncached page size.
- [Lighthouse][]: Check accessibility, desktop score, and mobile score.
- [Security Headers][securityheaders.com]: Check [HTTP][]
  security headers.
- [SSL Labs SSL Test][ssl-labs-ssl-test]: Check [TLS][] configuration.

I also manually check the site in the desktop and mobile versions of
[Chrome][] and [Firefox][].

I am investigated doing automated validation with [htmltest][],
[htmltidy][], and the [W3C validator][], but have not added them yet.

## Other

The [robots.txt][] for this site is from [here][robotstxt-ai] and
excludes all known [LLM][] crawlers.

I do not store credentials (e.g., the [HMAC][] key for the deployment
[web hook][]) in the [Git repository for this site][git-repo].

Write access to the [Git repository for this site][git-repo] is only
accessible via [SSH][].

[SSH][] access firewalled off and is only available via [Wireguard][].

Here are recommendations regarding [SSH][], in order of preference:

1. Only allow [SSH][] access via a [VPN][] and do not expose it to the
   public.
2. Only allow key-based authentication and disable password authentication.  Use an [Ed25519][] key, if possible.
3. Limit the [IP addresses][ip] which can connect to [SSH][] at the firewall.
4. Always disable remote `root` logins.

Note that these options are not mutually exclusive.

I have written about firewall configuration and [Wireguard][] elsewhere
on this site:

- [NFTables Examples][]
- [Wireguard is Awesome][]

## Updates

This section contains a list of updates to this article it was initially
published.

- 2024-06-03: [Apache Configuration][s-apache-configuration]: Make
  [SVGs][svg] render properly in [Firefox][] by adding a `<FileMatch>`
  directive for [SVGs][svg] which relaxes [Content-Security-Policy][] by
  allowing `style-src-attr 'unsafe-inline'`.  Without this exception
  inline [SVGs][svg] work fine, but viewing them directly just shows a
  black box.
- 2025-01-30: Added [robots.txt][] info to [Other](#other).
- 2025-03-31: Update wording to reflect the following changes: default to dark mode and remove [`prefers-color-scheme`][prefers-color-scheme] detection.

[s-hugo-configuration]: #hugo-configuration
  "Hugo Configuration"
[s-security-headers]: #security-headers
  "Security Headers"
[s-images]: #images
  "Images"
[s-deployment]: #deployment
  "Deployment"
[s-html]: #html
  "HTML"
[s-bulma-configuration]: #bulma-configuration
  "Bulma Configuration"
[s-apache-configuration]: #apache-configuration
  "Apache Configuration"
[s-other]: #other
  "Other"
[hugo]: https://gohugo.io/
  "Hugo static site generator."
[bulma]: https://bulma.io/
  "Bulma minimal CSS framework."
[mathy]: https://mathy.pmdn.org/
  "Online LaTeX editor."
[svg]: https://en.wikipedia.org/wiki/SVG
  "Scalable Vector Graphics vector image format (SVG)"
[png]: https://en.wikipedia.org/wiki/PNG
  "Portable Network Graphics lossless raster image format (PNG)"
[webp]: https://en.wikipedia.org/wiki/WebP
  "WebP raster image format"
[vim]: https://www.vim.org/
  "vim text editor"
[markdown]: https://en.wikipedia.org/wiki/Markdown
  "Markdown lightweight markup language"
[matplotlib]: https://matplotlib.org/
  "Python charting library"
[pngquant]: https://pngquant.org/
  "Command-line tool for \"lossy\" compression of PNG images"
[graphicsmagick]: http://www.graphicsmagick.org/
  "Command-line swiss army knife tool for image processing"
[php]: https://www.php.net/
  "PHP programming language"
[bootstrap icons]: https://icons.getbootstrap.com/
  "Free high quality open source icon library"
[jekyll]: https://jekyllrb.com/
  "Jekyll static site generator"
[git]: https://git-scm.com/
  "Free and open source distributed version control"
[javascript]: https://en.wikipedia.org/wiki/JavaScript
  "JavaScript scripting language"
[minify]: https://github.com/tdewolff/minify
  "Minifier Go library and command-line tool"
[mastodon]: https://en.wikipedia.org/wiki/Mastodon_(social_network)
  "Mastodon distributed social network"
[css]: https://en.wikipedia.org/wiki/CSS
  "Cascading Style Sheets"
[editing-this-article]: /files/articles/site-backend/0-editing.png
  "Editing this article with Vim and Firefox"
[jpeg]: https://en.wikipedia.org/wiki/JPEG
  "Joint Photographic Experts Group (JPEG) lossy raster image format"
[es2015]: https://en.wikipedia.org/wiki/ECMAScript_version_history#6th_Edition_%E2%80%93_ECMAScript_2015
  "Modern JavaScript"
[post-theme-switcher]: {{< relref "posts/2024-05-27-site-updates-dark-mode-and-about-pics" >}}
  "Blog post about dark mode and theme switcher"
[prefers-color-scheme]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
  "prefers-color-scheme media query"
[dark mode]: https://en.wikipedia.org/wiki/Light-on-dark_color_scheme
  "Dark mode"
[webhook]: https://github.com/adnanh/webhook
  "Lightweight webhook server to run shell commands"
[webhook.conf]: /files/articles/site-backend/webhook.conf.txt
  "Download webhook.conf"
[hmac]: https://en.wikipedia.org/wiki/HMAC
  "Hash-based message authentication code (HMAC)"
[apache]: https://apache.org/
  "Apache web server"
[apache-vhost.conf]: /files/articles/site-backend/pablotron.org.conf.txt
  "Download Apache virtual host configuration"
[apache-tls.conf]: /files/articles/site-backend/tls.conf.txt
  "Download Apache TLS configuration"
[web hook]: https://en.wikipedia.org/wiki/Webhook
  "HTTP-accessible callback"
[deferred]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
  "defer attribute of script tag"
[replay attacks]: https://en.wikipedia.org/wiki/Replay_attack
  "Network attack where a valid data transmission is repeated or delayed."
[tls]: https://en.wikipedia.org/wiki/Transport_Layer_Security
  "Transport Layer Security"
[post-receive]: https://git-scm.com/docs/githooks#post-receive
  "Git hook triggered on a remote repository after a push operation"
[git-repo]: https://git.pablotron.org/sites/pablotron.org/
  "Read-only web view of the Git repository for this site."
[deploy.rb]: https://git.pablotron.org/sites/pablotron.org/plain/bin/hook/deploy.rb
  "Deployment script invoked as a web hook."
[gen-logo.rb]: https://git.pablotron.org/sites/pablotron.org/plain/bin/logo-0/gen-logo.rb
  "Ruby script to generate animated SVG logo."
[post-png-compressors]: {{< relref "posts/2023-06-02-end-of-may-miscellany" >}}
  "Blog post with a review of several PNG compressors"
[bench-chart]: https://github.com/pablotron/fips203ipd/tree/main/scripts/bench-chart
  "Scripts to build and run fips203ipd bench utility with several combinations of backend and compiler, generate a summary CSV, and render a bar chart as an SVG."
[post-fips203ipd]: {{< relref "posts/2024-05-15-c11-fips203ipd-v0.6.md" >}}
  "Blog post about release of fips203ipd v0.6."
[script.js]: /files/articles/site-backend/script.js.txt
  "Unminified JavaScript for this site with some comments removed."
[http]: https://en.wikipedia.org/wiki/HTTP
  "Hypertext Transfer Protocol (HTTP)"
[https]: https://en.wikipedia.org/wiki/HTTPS
  "Hypertext Transfer Protocol Secure (HTTPS)"
[http/2]: https://en.wikipedia.org/wiki/HTTP/2
  "Hypertext Transfer Protocol, version 2 (HTTP/2)"
[post-headers-1]: {{< relref "posts/2021-10-19-hugo-csp-impedance-mismatch.md" >}}
  "Hugo/Content-Security-Policy Impedance Mismatch - October 19, 2021"
[post-headers-2]: {{< relref "posts/2021-10-21-tls-and-header-fixes.md" >}}
  "TLS and Header Fixes - October 21, 2021"
[post-headers-3]: {{< relref "posts/2021-10-25-the-nuclear-option-no-more-unsafe-inline.md" >}}
  "The Nuclear Option (No More unsafe-inline) - October 25, 2021"
[mod-http2]: https://httpd.apache.org/docs/current/mod/mod_http2.html
  "Apache module which provides HTTP/2 support."
[mod-proxy]: https://httpd.apache.org/docs/current/mod/mod_proxy.html
  "Apache module which provides proxy and reverse proxy support."
[mod-rewrite]: https://httpd.apache.org/docs/current/mod/mod_rewrite.html
  "Apache module which provides rules-based rewriting and redirecting engine."
[mod-macro]: https://httpd.apache.org/docs/current/mod/mod_macro.html
  "Apache module which provides macros within configuration files."
[mod-deflate]: https://httpd.apache.org/docs/current/mod/mod_deflate.html
  "Apache module which provides DEFLATE output compression."
[mod-brotli]: https://httpd.apache.org/docs/current/mod/mod_brotli.html
  "Apache module which provides brotli output compression."
[ssl-config]: https://ssl-config.mozilla.org/
  "Mozilla SSL Configuration Generator"
[tls 1.2]: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.2
  "TLS version 1.2"
[tls 1.3]: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.3
  "TLS version 1.3"
[ssl-labs-report]: https://www.ssllabs.com/ssltest/analyze.html?d=pablotron.org&hideResults=on
  "SSL Labs SSL Test results for this site."
[ssl-labs-ssl-test]: https://www.ssllabs.com/ssltest/
  "SSL Labs SSL Test"
[security-headers-quick-ref]: https://web.dev/articles/security-headers
  "Security headers quick ref"
[permissions-policy]: https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/
  "Goodbye Feature Policy and hello Permissions Policy!"
[permissions policy explainer]: https://github.com/w3c/webappsec-permissions-policy/blob/main/permissions-policy-explainer.md
  "Permissions Policy Explainer"
[referrer-policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
  "Documentation for \"Referrer-Policy: strict-origin-when-cross-origin\""
[content-security-policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
  "Content-Security-Policy header"
[hugo-shortcode-table]: https://github.com/pablotron/hugo-shortcode-table
  "Powerful Hugo table shortcode which emits HTML using CSS classes rather than inline style attributes."
[breach]: https://en.wikipedia.org/wiki/BREACH
  "Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext (BREACH)"
[cookies]: https://en.wikipedia.org/wiki/HTTP_cookie
  "Small blocks of data created by a web server when a user is browsing a web site."
[let's encrypt]: https://letsencrypt.org/
  "Free, automated, and open certificate authority (CA)"
[certbot]: https://certbot.eff.org/
  "Free, open source software tool for automatically using let's Encrypt certificates."
[certbot-dns-linode]: https://certbot-dns-linode.readthedocs.io/en/stable/
  "Certbot plugin which automates the process of completing an ACME dns-01 challenge by creating TXT records using the Linode API."
[securityheaders-report]: https://securityheaders.com/?q=pablotron.org&hide=on&followRedirects=on
  "Security headers report for this site from securityheaders.com"
[securityheaders.com]: https://securityheaders.com/
  "Free and easy to use tool to check the web site security headers."
[sass]: https://en.wikipedia.org/wiki/Sass_(style_sheet_language)
  "Syntactically Awesome Style Sheet (SASS)"
[style.sass.txt]: /files/articles/site-backend/style.sass.txt
  "Download SASS configuration"
[html]: https://en.wikipedia.org/wiki/HTML
  "HyperText Markup Language (HTML)"
[shortcode]: https://gohugo.io/content-management/shortcodes/
  "Snippets in content which call built-in or custom templates."
[chroma]: https://github.com/alecthomas/chroma
  "Chrome syntax highlighter"
[archives]: {{< relref "archive.md" >}}
  "Archived blog posts"
[go-import]: https://go.dev/ref/mod#vcs-find
  "HTML tag which allows Go to map a module name to a version control system repository."
[htmltest]: https://github.com/wjdp/htmltest
  "htmltest static HTML checker"
[w3c validator]: https://validator.w3.org/
  "W3C HTML validator"
[htmltidy]: https://www.html-tidy.org/
  "htmltidy"
[lighthouse]: https://developer.chrome.com/docs/lighthouse/overview/
  "Open source automated tool for improving the quality of web pages."
[chrome]: https://www.google.com/chrome/index.html
  "Google Chrome web browser."
[firefox]: https://www.mozilla.org/en-US/firefox/new/
  "Mozilla Firefox web browser."
[ssh]: https://en.wikipedia.org/wiki/Secure_Shell
  "Secure Shell"
[wireguard]: https://wireguard.com/
  "Fast and simple VPN which uses modern cryptography."
[vpn]: https://en.wikipedia.org/wiki/Virtual_private_network
  "Virtual Private Network"
[ip]: https://en.wikipedia.org/wiki/IP_address
  "Internet Protocol (IP) address"
[nftables examples]: {{< relref "articles/nftables-examples.md" >}}
  "NFTables Examples"
[wireguard is awesome]: {{< relref "posts/2021-11-06-wireguard-is-awesome.md" >}}
  "Wireguard is Awesome"
[ed25519]: https://en.wikipedia.org/wiki/EdDSA#Ed25519
  "Ed25519 digital signature algorithm."
[14kb]: https://endtimes.dev/why-your-website-should-be-under-14kb-in-size/
  "Why your website should be under 14kB in size"
[you-dont-need-js]: https://lexoral.com/blog/you-dont-need-js/
  "5 things you don't need JavaScript for"
[http compression]: https://en.wikipedia.org/wiki/HTTP_compression
  "HTTP compression"
[url]: https://en.wikipedia.org/wiki/URL
  "Uniform resource locator (URL)"
[progressive enhancement]: https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement
  "Web design which puts an emphasis on content first."
[robots.txt]:  https://en.wikipedia.org/wiki/Robots.txt
  "Robot exclusion protocol."
[robotstxt-ai]: https://robotstxt.com/ai
  "AI / LLM User-Agents: Blocking Guide"
[llm]: https://en.wikipedia.org/wiki/Large_language_model
  "Large Language Model"