Saturday, 18. March 2023Week 11
.: Docker registry facade with nginx

Found this inspiring blog post about how to use your own domain for Docker images. (via HN)

It explains how to use your own domain with redirects such that the Docker registry hosting the images can be changed easily. Your domain is only used for issueing HTTP redirects, so that the actual data storage and transfer happens directly with the Docker registry.

The blog post comes with a sample implementation for Caddy. As my server is running nginx, I used the following config snippet to achieve the same result:

server {
	listen 443 ssl;
	listen [::]:443 ssl;


	access_log	/var/log/nginx/;
	error_log	/var/log/nginx/;

	ssl_certificate		/etc/letsencrypt/live/;
	ssl_certificate_key	/etc/letsencrypt/live/;

	location / {
		return 403;

	location = /v2 {
		add_header Cache-Control 'max-age=300, must-revalidate';
		return 307$request_uri;
	location = /v2/ {
		add_header Cache-Control 'max-age=300, must-revalidate';
		return 307$request_uri;
	location = /v2/xway {
		add_header Cache-Control 'max-age=300, must-revalidate';
		return 307$request_uri;
	location /v2/xway/ {
		add_header Cache-Control 'max-age=300, must-revalidate';
		return 307$request_uri;

Quickly tested it with some docker pull commands and already integrated it into the build process of dnsupd.

10:36 | Linux | Permalink
Thursday, 26. January 2023Week 04
.: STRAYA 🇦🇺

Here's a bit older mashup. Happy Australia Day!

05:43 | Music | Permalink
Wednesday, 18. January 2023Week 03

Let's Encrypt recently introduced support for ACME-CAA.

I've now extended my existing CAA DNS entries with the ACME-CAA properties:

% dig +short -t CAA
0 issue "; accounturi=; validationmethods=http-01"
0 issue "; accounturi=; validationmethods=http-01"

The effect of this is that Let's Encrypt will only grant a signed TLS certificate if the request comes from one of my two accounts (authenticated with the corresponding private key).
If the certificate request comes from a different account, no TLS certificate will be granted.
This protects against man-in-the-middle attacks, specifically against attacks where someone between Let's Encrypt and my server would be trying to impersonate my server to obtain a signed TLS certificate.

In case you're wondering where to get the accounturi value from, it can be found in your account file:

% cat /etc/letsencrypt/accounts/*/regr.json
{"body": {}, "uri": ""}

23:06 | Networking | Permalink
Tuesday, 10. January 2023Week 02
.: JSON Feed

Added a JSON Feed to this blog (in additon to the existing RSS and Atom feeds):

To build the proper JSON file, I used this Jekyll template and the JSON Feed validator.

21:51 | Webdesign | Permalink
Tuesday, 3. January 2023Week 01
.: Get last 24h of logs with AWK

For a temporary log analysis task, I wanted to get the last 24h of logs from a Postfix logfile.
To achieve this I came up with the following AWK oneliner (which fails in spectacular ways around new years):

awk -F '[ :]+' 'BEGIN{m=split("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",d,"|"); for(o=1;o<=m;o++){months[d[o]]=sprintf("%02d",o)}} mktime(strftime("%Y")" "months[$1]" "sprintf("%02d",$2+1)" "$3" "$4" "$5) > systime()'

This is then used in a cronjob to get a pflogsumm summary of the last 24h:

cat /var/log/mail.log | awk -F '[ :]+' 'BEGIN{m=split("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",d,"|"); for(o=1;o<=m;o++){months[d[o]]=sprintf("%02d",o)}} mktime(strftime("%Y")" "months[$1]" "sprintf("%02d",$2+1)" "$3" "$4" "$5) > systime()' | pflogsumm
14:40 | Linux | Permalink
Saturday, 31. December 2022Week 52
.: Happy New Year 2023

As usual, Sydney is a bit ahead of us. Great memories, long time ago :-)

13:59 | Misc | Permalink
Sunday, 25. December 2022Week 51
.: Alpha Bravo Charlie

While closing an old account I had to communicate using the infamous NATO/ICAO phonetic alphabet (US banks like to exchange the 20+ character long IBANs via poor-quality call-center phonelines).

As it has been a while since I last used it, I created a handy table to quickly lookup the code words:

Special feature: when queried by curl (eg. without a text/html Accept header) it returns the table as plaintext :-)

# curl
A Alpha      S Sierra
B Bravo      T Tango
C Charlie    U Uniform
D Delta      V Victor
E Echo       W Whiskey
F Foxtrot    X X-ray
G Golf       Y Yankee
H Hotel      Z Zulu
I India      0 Zero
J Juliett    1 One
K Kilo       2 Two
L Lima       3 Three
M Mike       4 Four
N November   5 Five
O Oscar      6 Six
P Papa       7 Seven
Q Quebec     8 Eight
R Romeo      9 Niner

22:11 | Misc | Permalink
Sunday, 7. August 2022Week 31
.: How to Exit Vim

How to Exit Vim


22:19 | Linux | Permalink
Wednesday, 6. July 2022Week 27
.: Add node to MongoDB cluster

To add a new node to an existing MongoDB cluster, login to the mongo shell on the primary node and run the following command:


Similar to remove a node from the cluster, use:


22:14 | Linux | Permalink
Sunday, 19. June 2022Week 24
.: Custom nginx error pages

For quite some time I've been using custom nginx error pages on this site.
My approach so far was to generate a bunch of static HTML with the various error messages and then configure them for each corresponding HTTP status codes in nginx.
As there are quite a number of HTTP errors, I used a little shell script to generate the whole config and HTML, in the end I had a huge file with snippets like the one below.

error_page 429 @custom_error_429;
location @custom_error_429 {
	more_set_headers 'Content-Type: text/html';
	echo '<html>...</html>';

Now while implementing custom error pages for a different project, I tried to see if there is an easier way to do this.
Some searching lead to the One NGINX error page to rule them all article which describes an alternative approach leveraging the nginx SSI module to generate the error pages on the fly.

Instead of generating and defining a specific error page for each error, a single error page is used for all errors.

error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
           415 416 417 418 421 422 423 424 425 426 428 429 431 451 500
           501 502 503 504 505 506 507 508 510 511 /error.html;

location = /error.html {
	ssi on;
	root /var/www/default;

nginx provides the status code as variable to our error page, but we also need the error message to make it more userfriendly.
For this we define a mapping of status codes to the error messages.

map $status $status_text {
  400 'Bad Request';
  401 'Unauthorized';
  402 'Payment Required';
  403 'Forbidden';
  404 'Not Found';
  405 'Method Not Allowed';
  406 'Not Acceptable';
  407 'Proxy Authentication Required';
  408 'Request Timeout';
  409 'Conflict';
  410 'Gone';
  411 'Length Required';
  412 'Precondition Failed';
  413 'Payload Too Large';
  414 'URI Too Long';
  415 'Unsupported Media Type';
  416 'Range Not Satisfiable';
  417 'Expectation Failed';
  418 'I\'m a teapot';
  421 'Misdirected Request';
  422 'Unprocessable Entity';
  423 'Locked';
  424 'Failed Dependency';
  425 'Too Early';
  426 'Upgrade Required';
  428 'Precondition Required';
  429 'Too Many Requests';
  431 'Request Header Fields Too Large';
  451 'Unavailable For Legal Reasons';
  500 'Internal Server Error';
  501 'Not Implemented';
  502 'Bad Gateway';
  503 'Service Unavailable';
  504 'Gateway Timeout';
  505 'HTTP Version Not Supported';
  506 'Variant Also Negotiates';
  507 'Insufficient Storage';
  508 'Loop Detected';
  510 'Not Extended';
  511 'Network Authentication Required';
  default 'Something went wrong';

Now we have the status and the status_text variables available in our error.html page.

<h1><!--# echo var="status" default="" --> 
<!--# echo var="status_text" default="Something went wrong" --></h1>

09:48 | Webdesign | Permalink