There’s going to be an emergency PHP release made in just over 2 hours. It’s to address a critical security bug.
I’m not sure how long it’ll be before Ubuntu updates, but I will preload a bunch of posts here and on inphotos.org using WP Super Cache and turn off PHP until it’s updated. The files will be served by Apache’s mod_rewrite, so no PHP involved at all. Uncached pages won’t be so lucky, but at least some pages will still be live.
I made a WordPress plugin for my holidays. It’s called Travel Steps, and it turns a trip into a page with a map down one side and the story of each day down the other.
Every photo I take ends up in Immich, on my own server. My phone sends where I’ve been to Dawarich, on the same server. So after a trip I have all the photos and the whole route, sitting in two apps on my private server. The travel apps that make nice trip pages want your photos and your location on their servers. I’d rather keep them on mine.
How it works
You make a trip, give it a first and last day, and press Sync. Travel Steps asks Dawarich for your location over those days, and Immich for the photos you took. You can pick one Immich album instead. If you don’t use Immich at all, upload the photos to the trip. It will detect the start and end dates from the uploaded photos, or an Immich album too.
One sync, and a step for each day.
Sync makes a draft post for every day that has favourite photos. Each one is a “step”. It splits the day into places too. A new place starts when a favourite is more than 2 km from the last one and in a different town. Each place gets a heading and a block of photos, so all that’s left is the writing.
Each day is a post you write in the block editor. The photos under each heading come straight from Immich.
Place names come from GeoNames data that ships with the plugin, so naming a town doesn’t send your location anywhere. Immich only names a place from a photo’s own GPS. A photo from a camera without GPS gets placed from your Dawarich history instead, and named the same way.
Publish the days you want people to see. Drafts stay private, and so do their photos.
The map follows you as you scroll. Get to day 3 and day 3’s markers light up.
The distance at the top follows the whole route, flights included. GPS glitches that put you in the Atlantic for a second are dropped. Anything you’d have needed to travel faster than 1,080 km/h to reach counts as a glitch. Sorry, Concorde.
Your photos stay at home
Visitors never talk to Immich. WordPress fetches each photo, caches it and serves it itself, so Immich and the original files stay private. It caches a trip’s photos in the background after a sync. The trip keeps working when Immich is down. A daily sweep deletes cached photos older than a number of days you choose, whether a trip still uses them or not, and they’re fetched again the next time someone looks.
Photos you upload to a trip keep their GPS in the original, but it’s stripped from every copy WordPress makes, and only those copies are ever sent. A trip can have a password, and you can let some logged-in people skip it.
Pin, hide, cover
Hover over a photo to pin it (always show it), hide it, or make it the trip’s cover. The rest of the day’s photos sit behind a “Show more photos” link.
On phones, those buttons show up after you press Edit photos as illustrated by the images below.
Tap a photo and it opens full screen. On a phone you swipe from one photo to the next, edge to edge. On a desktop there are arrows, and the arrow keys work too.
Try it
Here’s a demo in WordPress Playground. It’s a week in the west of Ireland, running in your browser that installs nothing. Stand-in Immich and Dawarich servers feed the real sync code with photos from Unsplash. You’re logged in as an admin, so press Sync, pin and hide photos, and edit the days in the block editor. It all disappears when you close the tab.
The code is on GitHub. To install it, download travel-steps.zip from the latest release and upload it under Plugins → Add New. It needs WordPress 6.7 and PHP 8.0. Immich and Dawarich are both optional. It has been submitted to WordPress.org but if you read this blog, you’ll know how to install a plugin from a zip file!
WpApp
Travel Steps is built on WpApp, Alex Kirk’s framework for building apps on top of WordPress.
A plugin usually adds screens to wp-admin, or puts its pages inside your theme and hopes the CSS gets along. A WpApp app gets its own URLs instead (/travel-steps/ here), and its pages are plain HTML with no theme in the way. WordPress still does the boring work: users and logins, roles, the block editor, the REST API, cron and the database. WpApp adds the bits in between:
URL routing, using WordPress rewrite rules,
pages that don’t load your theme,
the admin bar on every page, with the app’s own icon (the green map at the top of my screenshots),
access control using WordPress capabilities.
You can start one with composer create-project akirk/create-wp-app my-app. Alex has a showcase of apps built with it, including a cookbook and a family wiki.
Built with Claude Code
I wrote Travel Steps with Claude Code. The first commit was on 11 October, and there are many more now. Most of the work went round the same loop. I file an issue, Claude fixes it test-first on a branch and opens a pull request, and other agents review it. I deploy it to my own server and try it. The tests run in WordPress Playground too, the same as the demo.
If you run Immich, give it a whirl. Bug reports welcome on GitHub 🙂
I’ve just released Media Picker for Immich on the WordPress.org plugin directory. It connects WordPress to a self-hosted Immich server so you can browse, search, and insert your photos and videos into posts without copying files around.
Immich
I run Immich at home. It’s where my photos now live. They’re organised, searchable, with facial recognition and AI search. My WordPress uploads directory is where photos used to go, and the two never talked to each other. This plugin fixes that.
How it works
Point the plugin at your Immich server and give it an API key. You can set a site-wide key or let each user configure their own to connect to their own Immich account.
If the site-wide key is blank, each user adds their own key on their profile page. All Immich API calls happen server-side.
Two ways to add media
Once configured, an Immich tab appears in two places.
The first is the Media Library grid. Switch to the Immich view and you can search, filter by person, and either Use or Copy assets into WordPress.
Use creates a virtual attachment. Nothing is copied; WordPress proxies the media from Immich on demand and caches it locally on first request. Your uploads directory stays lean.
Copy downloads the original file into wp-content/uploads/ as a normal attachment.
The same tab shows up in the “Select or Upload Media” dialog inside the post editor, so you can pull an Immich photo straight into a post without leaving the editor.
A few details worth mentioning
Videos work too. Proxied videos stream with seek support.
Lightbox. Proxied Immich images in posts open a full-resolution lightbox on click.
Local cache. Proxied media is cached to wp-content/cache/immich/ after the first fetch. Optional cleanup with a configurable lifetime.
Your server stays private. Immich only needs to be reachable from WordPress — not from the public internet. Visitors never connect to Immich directly.
When images are copied over, virtually or otherwise, you can insert them into a post like any other image, which also includes adding them to galleries in posts.
Get it
Install it from the WordPress plugin directory or search for “media picker for Immich” in the plugins page in WordPress.
Feedback and bug reports are welcome. Development is done on GitHub here.
At 4am yesterday morning I was awake. Luckily so, as I checked my email and saw a warning that my VPS had been running at 199% CPU for two hours. I went into my office and checked the logs. Someone had been hammering this site with HEAD requests since just after midnight with nearly 30,000 requests across different URLs in four hours.
If they’d hit the same page repeatedly, cached responses would have handled it. But by requesting thousands of distinct URLs, each request generated a fresh cache file, forcing PHP and the database to do real work every time. My small server was coping, but the load average was high.
I blocked the offending IP in .htaccess immediately. That stopped requests from reaching PHP, but I wanted a proper defence, something that would catch this kind of abuse automatically next time.
I already had Fail2Ban installed. I’d just never configured it for web server attacks. Time to do that.
Fail2Ban works by watching log files for patterns, then banning IPs that match too often. I needed two files: a filter to match Apache access log lines, and a jail to define the thresholds.
First, the filter at /etc/fail2ban/filter.d/apache-ratelimit.conf:
[Definition]
# Matches any request line in Apache access log (combined/common format)
failregex = ^<HOST> -."(GET|HEAD|POST|PUT|DELETE|OPTIONS) .
ignoreregex =
This matches every request in the log.
The jail decides what volume of requests counts as abuse. It lives at /etc/fail2ban/jail.d/apache-ratelimit.conf:
[apache-ratelimit]
enabled = true
filter = apache-ratelimit
logpath = /var/www/logs/access.log
# Triggers a ban after too many requests in a short window
maxretry = 500
findtime = 30
# Ban for 24 hours
bantime = 86400
# Use iptables to drop packets from banned IPs
banaction = iptables-multiport
port = http,https
protocol = tcp
# Allowlist trusted networks
ignoreip = 127.0.0.1
The logic: if a single IP makes more than 500 requests in 30 seconds then ban it for 24 hours. Adjust maxretry and findtime to suit your traffic patterns. Legitimate crawlers and real users won’t come close to these numbers, but an attacker blasting thousands of requests will trip the threshold fast. These are not the numbers I’m using.
The ignoreip directive keeps trusted networks (monitoring services, your own IPs) from getting caught.
The changes to Fail2Ban are activated with the following command (as root):
systemctl restart fail2ban
And I verified it was running by checking the status:
fail2ban-client status apache-ratelimit
Within a couple of minutes, the offending IP appeared in the banned list. I checked the access log, and there was nothing more from that IP. The requests weren’t just being rejected by Apache; iptables was dropping the packets before they reached the web server at all. That’s the key advantage over an .htaccess block: banned traffic never touches Apache.
A VPS with limited resources can’t absorb a flood of uncached requests. Caching helps with repeat visits to the same page, but an attacker who rotates URLs defeats that entirely. Fail2Ban shifts the defence from the application layer down to the network layer, where it’s cheap to enforce.
If you run a small site on a VPS, configure fail2ban for your web server. It takes ten minutes and it works.
Anyway, I presume whoever did that attack is reading this post since they seem to like my blog so much. Why did you do it?
Running a photography club competition used to mean juggling spreadsheets, chasing emails, and manually tallying votes. I built Photo Competition Manager to change that.
This free WordPress plugin handles everything your club needs: member management, secure uploads, flexible voting, and beautiful results displays.
This will suit a photography club that uses WordPress for their website and that has a projector for their meetings. If you already run a photo league with projected images or prints but collect votes from your club members using pen and paper you’re the perfect candidate to try this plugin.
This is the very first release of this plugin. It has been used a couple of times to run several test photo competitions and one monthly league competition. However, bugs happen. I would love if your photography club used this plugin but I will need feedback to improve it. Run a couple of test competitions first to get used to how it works.
Getting Started in Minutes
The setup wizard creates all the required pages automatically. Select the pages you need, click a button, and you’re ready to go.
Manage Your Membership
The Members page is your central roster. Add members individually or bulk import from CSV. Assign grades, track status, and send magic-link upload invitations with a single click.
Each member gets a unique upload URL—no passwords to remember, no accounts to create. They click the link and upload.
Create and Track Competitions
The competitions dashboard shows a list of competitions, allowing you to edit them and send out upload emails to members. Only one competition may be active at a time, but a competition may have more than one category.
Create competitions with custom categories (Colour, Mono, Projected, etc), grade divisions (Beginner, Intermediate, Advanced), and submission quotas. Each competition can have its own rules without affecting your defaults.
Watch Submissions Roll In
As members upload, the submissions page displays thumbnails of every entry. You’ll see who’s submitted, which categories are filling up, and whose photos are still missing.
A Frictionless Upload Experience
When members click their magic link, they land on a clean upload form. They select their category, choose their image, and submit. The progress banner shows upload status in real time.
The plugin handles validation automatically—file types, dimensions, and quota limits are all enforced before the image is accepted.
Complete Control Over Voting
The voting controls page is your competition command centre. From here you can:
Open and close voting for each category independently.
Toggle the results display on or off.
Enable slideshow mode for in-person club nights.
Disable uploads once the submission deadline passes.
Show a QR code for members to scan with their phones to vote.
In the competition settings, you choose between token-based voting (unique links per member) or password-protected public voting. The voting controls page features a full-screen slideshow mode where members can vote together during meetings.
On the night of the competition we do the following:
Close uploads and hide results.
Show the slideshow with a 10 or 15-second delay as a preview.
Display the QR code and make sure every member can scan it and open the voting page.
Open voting and show the slideshow again.
When everyone has voted, close voting.
2 members of the club will then go through the images and offer a critique, using the slideshow again but in manual mode this time.
Repeat for the next category.
When all the voting is done, toggle the results page on and display the “top 3” results, and then the anonymous results page on the projector.
Finally, an email is sent out to all members showing their score, ranking in their grade and the votes they received. Names are not attached to the votes for obvious reasons.
Voting By Phone
Members can vote on the competition using their phones after they have scanned the QR code on the voting controls page.
Results That Make Sense
When voting closes, the results dashboard breaks down every score by member grade. See vote distributions, identify your winners, and export everything to CSV for your records.
The frontend includes a responsive top-3 podium display perfect for announcing winners, plus full results tables with filtering by grade and category.
Built for Photography Clubs
Photo Competition Manager was built specifically for the way camera clubs actually run competitions:
Magic-link authentication means members don’t need WordPress accounts to upload.
Grade-based scoring supports clubs that split their membership by experience level.
Slideshow mode turns any screen into a projection-ready display.
CSV export keeps your archives intact.
Get Started Free
Photo Competition Manager is free and GPL. Install it from the WordPress plugin directory or from the plugins page on your WordPress site in the usual way. Navigate to the Competitions menu to get started.
When I started working at Automattic, it was just me and Matt, and two servers. A web server and a MySQL server. I knew the root password to WordPress.com. I needed it as I spent a lot of time tuning the MySQL server in those days, but I was thrilled when we got some real systems people on board like Barry. I have to admit to a certain sadness when I ran sudo and the password didn’t work, however.
Automattic in 2006, when my luggage was delayed and I was wearing a British Airways tshirt.
When I started working at Automattic, many of my colleagues I work with now, were still in school. There was a time at the start of this year that my team had the first employee and the latest employee on it. I didn’t have any grey hair then, and well, I have some now, and I make jokes about the “old days” but there are quite a few of us boldermatticians.
I spent most of my time working in Vim, in an SSH session, but that’s changed to VS Code and Cursor in recent years. I tried the Vim extensions for those, but they never felt as good as the original.
Now, it’s the upstart AIs that are disrupting everything related to my job, but while it certainly feels like it’s making me a lot more productive, apparently it’s making me dumb too. Time will tell. Andrej Karpathy uses a number of analogies in this video at Y Combinator, but one thing that resonated with me was his comparing the state of AI to computing in the 60s. There were massive mainframes that people used thin clients (or punch cards!) to interact with them. In 2025, the AI is this brain in the cloud we talk to and ask questions of in a chat window. What’s it going to be like in another twenty years?
The Speculative Loading plugin for WordPress is a plugin you should probably try out on your site, especially if you use WP Super Cache or Jetpack Boost to cache things. It uses the new speculation API that Chrome/Edge supports to load pages in the background if you even hover over a link.
It will dynamically prefetch or prerender pages before they’re requested by the visitor on your site, which means that the page will show instantly when the visitor actually clicks the link.
It doesn’t work in Firefox yet, but it won’t hurt either, as the browser will just ignore the extra bits and pieces added to the page.
The default “moderate” eagerness works fine for me. The “eager” setting appeared to load links if the cursor got anywhere near them, which was a little too aggressive.
You won’t notice your browser loading the page in the Network tab of the webdev tools, but if you tail your access_log, you’ll see the requests go through when you hover over the links.
Browse around this site, or take a look at my photoblog for a feel of what it does.
There’s more info in the make blog post about it, and this insightful comment about the wastefulness of loading pages that might not be used, especially for visitors on limited data plans, or low powered devices. That’s definitely something to think about before using this plugin. I may yet remove it later, and I’ll update this post if I do.
Today saw the release of updates to two plugins I work on. The first, unsurprisingly, is WP Super Cache, and it’s a bug fix release. It fixed a PHP8.2 warning, adjusted some labels on the settings page, solved a problem with “late init” and POST requests, and some other changes, but the major news I want to share is not about that plugin at all.
I’m on the Jetpack Boost team in Automattic, and for the last month or so, we’ve been working on adding a full-page cache to the plugin!
Jetpack Boost is already a pretty remarkable plugin, with what it does to CSS, JavaScript and images, so a page cache was an obvious next step.
If you’re already using WP Super Cache, but you’re not using any of the advanced features, I think you should give Jetpack Boost a go. It works really well at serving cached pages fast. Jetpack Boost will be a drop-in replacement if you have these features enabled in WP Super Cache:
Simple caching
Caching Disabled for logged in visitors
Garbage collection set to one hour.
You’ll have to disable WP Super Cache first before trying Boost Cache, as they both rely on the same mechanism to load. Disable Jetpack Boost to go back to WP Super Cache. You can enable Boost again, without using the Cache module, and the two plugins will happily co-exist.
I’ve been using it here for the last two weeks, and it has worked flawlessly. I post images daily on my photoblog, and they get shared to Mastodon, where the expected deluge of requests from hundreds of servers happens each time. Between Jetpack Boost and WP Rest Cache, my server barely noticed. Just like with WP Super Cache.
“If it performs just like WP Super Cache, why should I use Boost?” I hear you asking. For some, you’ll need the extra features of WP Super Cache, but otherwise, Boost may be just what you need. Apart from the cache, it also has remarkable features that will squash your CSS and JavaScript in various ways that load super quick. Give it a go, and let WP Super Cache handle the cache if you need to.
If you’re still here, read on, and I’ll give you a whirlwind tour.
Boost supports an exception list like WP Super Cache does, and logging of activities in a similar, but easier to use way.
To simplify caching, only requests by logged out visitors are cached now, but that’s the recommended way of using WP Super Cache too.
Developers: like WP Super Cache, it also supports the “DONOTCACHEPAGE” constant. If you define that while a page is being created, then Jetpack Boost won’t cache it. If you can enable it before the page loads, then the plugin won’t serve a cached page either, but that will be difficult since the cache system loads so early.
To learn more, there’s a fine manual to read. It explains in detail how to enable the cache and how to use it. Yes, I’m biased, but I think you should give Jetpack Boost a go! Caching helps a lot, but the extra CSS and JavaScript features help render the page quickly once loaded.
Pebble, aka t2.social, was a short lived social network like Twitter. Last week they sent out emails to all their users to tell them that the site was shutting down on November 1st. I first came across it thanks to Topgold, but it was always a small site. In a crowded section of the Internet, another Twitter clone would have a tough time competing.
I hadn’t posted much there and wasn’t going to download my data, but this post by Eugen Rochko caught my attention and reminded me to go visit.
If #Pebble was part of the social web, they would have had a network of 1.8M active users, not 1,000, and perhaps wouldn’t have had to shut down.
Eugen Rochko
Maybe it would have survived, but it would have had to be extra special and offer some compelling features to compete with all the “free” Fediverse servers out there. The Activity Pub plugin for WordPress recently hit version 1.0 and was launched on WordPress.com too, so potentially millions of new Fediverse sites are coming online, all of which are on more mature software.
Close
Ad-blocker not detected
Consider installing a browser extension that blocks ads and other malicious scripts in your browser to protect your privacy and security. Here are a few options.
uBlock Origin is a free, open source, ad blocker for your browser.
Use pi-hole if you have a spare Raspberry Pi on your network.
Set the private DNS settings on your phone to dns.adguard.com to block adverts and trackers.