<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Web Development on ma.ttias.be</title><link>https://ma.ttias.be/categories/web-development/</link><description>Recent content in Web Development on ma.ttias.be</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>mattias@ma.ttias.be (Mattias Geniar)</managingEditor><webMaster>mattias@ma.ttias.be (Mattias Geniar)</webMaster><lastBuildDate>Thu, 04 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ma.ttias.be/categories/web-development/index.xml" rel="self" type="application/rss+xml"/><item><title>Running HTTP/3 with Caddy in 2026: it just works</title><link>https://ma.ttias.be/running-http3-with-caddy-2026/</link><pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/running-http3-with-caddy-2026/</guid><description>&lt;p&gt;When I &lt;a href="https://ma.ttias.be/how-run-http-3-with-caddy-2/"&gt;wrote about running HTTP/3 with Caddy in 2020&lt;/a&gt;
, it was a ritual. You opted in with an &lt;code&gt;experimental_http3&lt;/code&gt; global option, the protocol was still draft &lt;code&gt;h3-27&lt;/code&gt;, and to test it you needed a custom-compiled curl and a browser nightly with the right flags flipped. It worked, but it was clearly the bleeding edge.&lt;/p&gt;</description></item><item><title>QUIC and HTTP/3 in 2026: from Google experiment to IETF standard</title><link>https://ma.ttias.be/quic-http3-in-2026/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/quic-http3-in-2026/</guid><description>&lt;p&gt;Ten years ago I wrote about &lt;a href="https://ma.ttias.be/googles-quic-protocol-moving-web-tcp-udp/"&gt;Google&amp;rsquo;s QUIC protocol&lt;/a&gt;
, back when it was an experiment you could realistically only test against Google&amp;rsquo;s own servers. I was excited about it, and I ended that post hoping the spec would get standardised and show up in other browsers and servers.&lt;/p&gt;</description></item><item><title>Caching get_certificate lookups in Caddy</title><link>https://ma.ttias.be/caching-get-certificate-lookups-in-caddy/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/caching-get-certificate-lookups-in-caddy/</guid><description>&lt;p&gt;At &lt;a href="https://ohdear.app" target="_blank" rel="noopener noreferrer"&gt;Oh Dear&lt;/a&gt;
, our &lt;a href="https://ohdear.app/features/status-pages" target="_blank" rel="noopener noreferrer"&gt;status pages&lt;/a&gt;
can run on a customer&amp;rsquo;s own domain, and we&amp;rsquo;ve always served on-demand ACME certificates for them: someone points their domain at us, and &lt;a href="https://caddyserver.com" target="_blank" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt;
provisions a Let&amp;rsquo;s Encrypt certificate for it automatically. We&amp;rsquo;re now adding a second option, letting customers bring their &lt;em&gt;own&lt;/em&gt; certificate. For that, Caddy doesn&amp;rsquo;t provision anything; it fetches the certificate from an HTTP backend in our app.&lt;/p&gt;</description></item><item><title>Serving HTTP/2 and HTTP/3 with Nginx in 2026</title><link>https://ma.ttias.be/serving-http2-http3-nginx-2026/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/serving-http2-http3-nginx-2026/</guid><description>&lt;p&gt;Back in 2016 I &lt;a href="https://ma.ttias.be/run-nginx-proxy-docker-container-http2/"&gt;wrapped nginx in a Docker container&lt;/a&gt;
just to get HTTP/2 working. Not because I love Docker, but because the server&amp;rsquo;s OpenSSL was too old to do ALPN, and the cleanest way to borrow a modern OpenSSL was to lift one out of an Alpine container. It worked, but it was a hack, and I knew it at the time.&lt;/p&gt;</description></item><item><title>Finding Dutch audio across streaming services</title><link>https://ma.ttias.be/finding-dutch-audio-across-streaming-services/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/finding-dutch-audio-across-streaming-services/</guid><description>&lt;p&gt;We have 4 streaming services at home: Netflix, Disney+, Prime Video, and Apple TV+. Try finding &lt;em&gt;which&lt;/em&gt; films and series are actually available with Dutch audio.&lt;/p&gt;</description></item><item><title>How we implemented a mobile-friendly Oh Dear UI with AI</title><link>https://ma.ttias.be/mobile-friendly-oh-dear-ui-with-ai/</link><pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/mobile-friendly-oh-dear-ui-with-ai/</guid><description>&lt;p&gt;We just shipped a &lt;a href="https://ohdear.app/news-and-updates/oh-dear-is-now-mobile-friendly" target="_blank" rel="noopener noreferrer"&gt;mobile-friendly version of Oh Dear&lt;/a&gt;
. It touched 226 files, added over 5,000 lines, and modified 160+ Blade templates. The PR took three weeks from first commit to merge.&lt;/p&gt;</description></item><item><title>Web development is fun again</title><link>https://ma.ttias.be/web-development-is-fun-again/</link><pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/web-development-is-fun-again/</guid><description>&lt;p&gt;I remember when PHP 4 was a thing. jQuery was new and shiny. Sites were built with tables, not divs. Dreamweaver felt like a life hack. Designs were sliced in Photoshop. Databases lived in phpMyAdmin.&lt;/p&gt;</description></item><item><title>Oh Dear 2.0 launched</title><link>https://ma.ttias.be/oh-dear-2-0-launch/</link><pubDate>Thu, 29 Sep 2022 14:51:53 +0200</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/oh-dear-2-0-launch/</guid><description>&lt;p&gt;I couldn&amp;rsquo;t be prouder to showcase &lt;a href="https://ohdear.app/" target="_blank" rel="noopener noreferrer"&gt;Oh Dear 2.0&lt;/a&gt;
, our all-in-one monitoring solution for websites. Over the past year, we&amp;rsquo;ve redesigned the entire service and have started implementing the designs. And now, it&amp;rsquo;s ready.&lt;/p&gt;</description></item><item><title>How do we find users for our SaaS?</title><link>https://ma.ttias.be/how-are-users-finding-our-saas/</link><pubDate>Tue, 09 Jun 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/how-are-users-finding-our-saas/</guid><description>&lt;p&gt;After more than 2 years of building &lt;a href="https://ohdear.app/" target="_blank" rel="noopener noreferrer"&gt;Oh Dear&lt;/a&gt;
, I still struggle with the most fundamental question: &lt;strong&gt;how are users finding our application and where should we focus our marketing efforts to maximize that?&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>What else can you stuff in a certificate chain?</title><link>https://ma.ttias.be/certificate-chain-stuffing/</link><pubDate>Mon, 25 May 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/certificate-chain-stuffing/</guid><description>&lt;p&gt;I recently learned that quite a few (old) root certificates are going to expire, and &lt;a href="https://ohdear.app/blog/resolving-the-addtrust-external-ca-root-certificate-expiration" target="_blank" rel="noopener noreferrer"&gt;many websites still send those along in the TLS handshake&lt;/a&gt;
.&lt;/p&gt;</description></item><item><title>Selecting all checkboxes on a page with the Developer Console</title><link>https://ma.ttias.be/selecting-all-checkboxes-developer-console/</link><pubDate>Sun, 03 May 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/selecting-all-checkboxes-developer-console/</guid><description>&lt;p&gt;A quick reminder to myself that the Developer Console in Chrome or Firefox is useful to mass-select a bunch of checkboxes, if the site doesn&amp;rsquo;t have a &amp;ldquo;select all&amp;rdquo;-option (which really, it should).&lt;/p&gt;</description></item><item><title>Build &amp; test your Laravel project via Bitbucket Pipelines</title><link>https://ma.ttias.be/build-test-laravel-project-bitbucket-pipelines/</link><pubDate>Wed, 29 Apr 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/build-test-laravel-project-bitbucket-pipelines/</guid><description>&lt;p&gt;This post will show you how to use &lt;a href="https://bitbucket.org/product/features/pipelines" target="_blank" rel="noopener noreferrer"&gt;Bitbucket Pipelines&lt;/a&gt;
to build and test your Laravel Project in a docker container.&lt;/p&gt;</description></item><item><title>Set up a custom 404 page for static sites with Caddy 2</title><link>https://ma.ttias.be/set-up-custom-404-page-static-sites-caddy/</link><pubDate>Sun, 26 Apr 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/set-up-custom-404-page-static-sites-caddy/</guid><description>&lt;p&gt;Here&amp;rsquo;s a quick example of setting up a custom 404 landing page if you use Caddy V2 to serve static sites, like this blog.&lt;/p&gt;</description></item><item><title>Someone, somewhere, is trying to break into your app</title><link>https://ma.ttias.be/someone-somewhere-is-trying-to-break-into-your-app/</link><pubDate>Tue, 21 Apr 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/someone-somewhere-is-trying-to-break-into-your-app/</guid><description>&lt;p&gt;It&amp;rsquo;s good to be reminded of the fact that the internet is, in fact, a pretty hostile place.&lt;/p&gt;</description></item><item><title>How to run HTTP/3 with Caddy 2</title><link>https://ma.ttias.be/how-run-http-3-with-caddy-2/</link><pubDate>Fri, 10 Apr 2020 06:30:00 +0100</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/how-run-http-3-with-caddy-2/</guid><description>&lt;p&gt;I &lt;a href="https://ma.ttias.be/migrating-caddy-2/"&gt;just migrated this webserver to Caddy 2&lt;/a&gt;
and with it, enabled HTTP/3 support. This post will give a short explanation how you can do that.&lt;/p&gt;</description></item><item><title>Migrating to Caddy 2</title><link>https://ma.ttias.be/migrating-caddy-2/</link><pubDate>Fri, 10 Apr 2020 06:00:00 +0100</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/migrating-caddy-2/</guid><description>&lt;p&gt;Last week, the &lt;a href="https://github.com/caddyserver/caddy/releases" target="_blank" rel="noopener noreferrer"&gt;first Release Candidate of Caddy 2&lt;/a&gt;
saw the light of day. I don&amp;rsquo;t usually like to run production environments on beta software, but for Caddy I wanted to make an exception&lt;/p&gt;</description></item><item><title>Forcing a Content-Type header with Guzzle's form_params</title><link>https://ma.ttias.be/force-content-type-header-guzzle-form-params/</link><pubDate>Mon, 09 Mar 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/force-content-type-header-guzzle-form-params/</guid><description>&lt;p&gt;I just lost an hour or 2 of my life to this, so I figure I&amp;rsquo;ll do a small write-up to save &lt;em&gt;future me&lt;/em&gt; from having to do the same dance.&lt;/p&gt;</description></item><item><title>I found a loophole to prevent those pesky cookie notices 👀</title><link>https://ma.ttias.be/loophole-cookie-notices/</link><pubDate>Thu, 27 Feb 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/loophole-cookie-notices/</guid><description>&lt;p&gt;Have you noticed how this page doesn&amp;rsquo;t have a cookie pop-up? Nothing to accept before you can read the content?&lt;/p&gt;</description></item><item><title>Adding a sticky table of contents in Hugo to posts</title><link>https://ma.ttias.be/adding-a-sticky-table-of-contents-in-hugo-to-posts/</link><pubDate>Wed, 26 Feb 2020 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/adding-a-sticky-table-of-contents-in-hugo-to-posts/</guid><description>&lt;p&gt;&lt;a href="https://www.bram.us" target="_blank" rel="noopener noreferrer"&gt;Bram Van Damme&lt;/a&gt;
recently &lt;a href="https://www.bram.us/2020/01/10/smooth-scrolling-sticky-scrollspy-navigation/" target="_blank" rel="noopener noreferrer"&gt;shared a really cool method of showing a table of contents&lt;/a&gt;
on a page and I&amp;rsquo;ve wanted to implement it on this blog ever since.&lt;/p&gt;</description></item><item><title>Making a JSON prettifier in pure JavaScript</title><link>https://ma.ttias.be/making-a-json-prettifier-in-pure-javascript/</link><pubDate>Fri, 20 Dec 2019 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/making-a-json-prettifier-in-pure-javascript/</guid><description>&lt;p&gt;A few weeks ago I toyed with the idea of making a JSON prettifier myself. I often use these to re-format and debug a minified JSON payload.&lt;/p&gt;</description></item><item><title>cron.weekly is coming back!</title><link>https://ma.ttias.be/cron-weekly-is-coming-back/</link><pubDate>Sun, 01 Dec 2019 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/cron-weekly-is-coming-back/</guid><description>&lt;p&gt;Once upon a time I wrote a weekly newsletter on Linux, open source &amp;amp; web development. Basically I wrote about the things that interested me.&lt;/p&gt;</description></item><item><title>A Caddyfile config example for Laravel</title><link>https://ma.ttias.be/caddyfile-config-example-for-laravel/</link><pubDate>Mon, 25 Nov 2019 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/caddyfile-config-example-for-laravel/</guid><description>&lt;p&gt;I&amp;rsquo;ve been using the &lt;a href="https://caddyserver.com/" target="_blank" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt;
webserver for all my projects lately. Here&amp;rsquo;s my current default config for a Laravel project.&lt;/p&gt;</description></item><item><title>Remove index.php from the URL in Laravel</title><link>https://ma.ttias.be/remove-index-php-from-the-url-in-laravel/</link><pubDate>Thu, 21 Nov 2019 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/remove-index-php-from-the-url-in-laravel/</guid><description>&lt;p&gt;If you have a Laravel project, you might be surprised to find your routes are probably available on a number of different URLs.&lt;/p&gt;</description></item><item><title>How to prevent content-spoofing in Apache's 404 error pages</title><link>https://ma.ttias.be/prevent-content-spoofing-apache-404-error-pages/</link><pubDate>Wed, 20 Nov 2019 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/prevent-content-spoofing-apache-404-error-pages/</guid><description>&lt;p&gt;We had an interesting security report for our &lt;a href="https://ohdear.app/" target="_blank" rel="noopener noreferrer"&gt;Oh Dear monitoring service&lt;/a&gt;
. An attacker could load a specific URL and trigger a 404 page in which &lt;em&gt;they&lt;/em&gt; controlled the output.&lt;/p&gt;</description></item><item><title>A step-by-step guide on migrating from WordPress to Hugo</title><link>https://ma.ttias.be/step-by-step-guide-migrating-wordpress-to-hugo/</link><pubDate>Tue, 12 Nov 2019 00:00:00 +0000</pubDate><author>mattias@ma.ttias.be (Mattias Geniar)</author><guid>https://ma.ttias.be/step-by-step-guide-migrating-wordpress-to-hugo/</guid><description>&lt;p&gt;I just finished the last bits of migration for this site. It involved moving my podcasts from the &lt;code&gt;podcast.sysca.st&lt;/code&gt; domain to &lt;a href="https://ma.ttias.be/syscast/"&gt;a dedicated overview on this site&lt;/a&gt;
.&lt;/p&gt;</description></item></channel></rss>