— —Blog
Notes from the dock.
Practical writing on legacy WordPress, Drupal, Joomla, Magento and custom-PHP sites — and the tooling around them. Tuesdays and Thursdays.
— Latest —
132
-
Joomla 4 500 after Akeeba restore: the missing action_logs fix
A routine Akeeba restore. A blank white 500. Forty minutes of grep, schema reconstruction and one quiet INSERT later, the Joomla 4 site was back. -
PHP 7.4 to 8.2 upgrade: a pre-flight audit checklist
You said yes to the PHP 8.2 jump on a ten-year-old WordPress install. Before the maintenance window opens, here is the audit that keeps it boring. -
Drupal watchdog by hand: severity, type, four queries
The dblog admin page caps at 50 rows and aggregates nothing. The watchdog table itself answers the questions the UI cannot. Three columns and four queries. -
wp-config.php debug constants: the nine we always set
A site lands in your inbox: SFTP creds, vague symptoms, no documentation. Before reading a line of someone else's plugin code, we set nine wp-config constants. -
WordPress multisite migration: the one-Sunday playbook
A Plesk box on PHP 7.4 with a 12-site multisite, a fresh VPS waiting at Hetzner, and a Sunday from 09:00 to 17:00 to get the cutover done before Monday traffic. -
Symfony 6 intranet migration: eleven weekends, zero downtime
Eleven weekends, one strangler proxy, and a timesheet form that never went dark. How an in-house team moved a 2011 custom-PHP intranet onto Symfony 6 without anyone noticing. -
Magento 2 cron_schedule at 14M rows: a Friday postmortem
A Magento 2 shop's cron_schedule table hit 14 million rows on a Friday afternoon. Here is the prune, the lock duration we accepted, and the indexer config that stopped the regrowth. -
Contact form spam: a 30-minute incident walkthrough
23:41 on a Tuesday. A legacy WordPress contact form just became an open relay and the host has fifteen minutes of patience. Here is the thirty-minute IR. -
WordPress nonces: what wp_verify_nonce actually does
The form works in the morning, breaks in the afternoon, and nobody can reproduce it on demand. The bug is in how you read what wp_verify_nonce returns. -
osCommerce to WooCommerce: six weekends, no SEO loss
A 14-year-old osCommerce store moved onto WooCommerce in six weekends. The URL map, the specials-to-sale-price bridge, and the SEO that kept its rankings. -
PHP-FPM pool sizing: 2GB, 4GB and 8GB VPS, compared
A WooCommerce shop crashes at lunch. PHP-FPM is at 100% pool capacity and the host wants you to size up the VPS. Before you click, here's the math. -
Magento 1 AI product copy: a staging shape that holds
A read-only staging mirror is the cheapest insurance against AI-generated product copy that overwrites the wrong SKU in a 2014-vintage Magento store. -
Inheriting a legacy site: the one-page handover audit
You inherited a WordPress site from another agency. The code looks fine. Then the SSL silently fails on a server nobody mentioned. Here is the audit we run. -
Cache-Control leak: the missing word that cached sessions
At 16:47 a customer screenshotted someone else's order history in his own dashboard. The bug was a Cache-Control header on the PHP account page, missing one word. -
Joomla 3 #__extensions table: a six-column field guide
Six columns in #__extensions tell you everything Joomla actually loads. A field guide for the moment you take over a site whose admin won't open. -
mysqldump flags for legacy MySQL: the .my.cnf cheatsheet
The .my.cnf and mysqldump flags we drop on every legacy MySQL box before an export, and the three that have saved a restore at two in the morning. -
Drupal 7 to static on Cloudflare Pages: a 2-hour playbook
Drupal 7 reached end of life. You inherited one. Here's a two-hour route to a static export on Cloudflare Pages with the contact form still working. -
Magento sales_order_grid: the staged 4GB shrink we ran
A Dutch agency we work with had a sales_order_grid pushing 4GB after Black Friday. Here is the staged shrink that took it back to lean, with every archived order intact. -
WooCommerce double-charge: tracing a duplicate Stripe hook
Two payment intents, three seconds apart, same order, same card. An incident walkthrough of a duplicate woocommerce_payment_complete hook hiding in mu-plugins. -
Legacy site monitoring: the four-signal stack for solo shops
Thirty legacy sites, one person, and a Sunday morning that started with a 'checkout broken since Friday' Slack. Four signals would have caught it on Thursday. -
Statamic migration freeze: a five-day editorial playbook
The staged content-freeze playbook for a five-day legacy-to-Statamic cutover: editorial lock, three diff checkpoints, the .htaccess that ships. -
PHP session race: the case of the wrong invoice PDF
A Dutch agency we work with rang at 23:41. Their custom-PHP billing portal had emailed the wrong invoice PDF to a customer. We blamed session_start for six hours. -
Magento 1 chatbot: the read-only shape that survives
A client wants a chatbot bolted onto a 2017 Magento 1 store. The trick is keeping it read-only, off the checkout path, and out of the order tables. -
Xdebug 3 over an SSH tunnel: the SFTP-only setup
A working Xdebug 3 setup against a remote SFTP-only host, with the tunnel direction, path map and idekey gotchas spelled out so you don't lose an afternoon. -
Curl recon for legacy sites: seven one-liners before quoting
An agency drops a URL into the brief at 23:41 and asks for a hardening quote by morning. Seven curl one-liners take five minutes and size the work honestly. -
Joomla 3 image library to S3: a 22GB migration log
How we moved a 22GB Joomla 3 image library to S3 over one weekend, without breaking a single article reference or noticing a flicker in the access log. -
WordPress functions.php hack: a four-pass cleanup playbook
A hacked theme functions.php is rarely alone. Here is the four-pass cleanup we run on WordPress sites before anyone goes near wp-options or wp-posts. -
Reading mysqlbinlog: a field guide to row-format events
An agency lead pings at 23:41: someone swapped wp_options.siteurl, the web logs are clean. Here is how to read mysqlbinlog row events and find them. -
Drupal 9 502 for editors only: a session table outage
A Drupal 9 site started 502ing only for logged-in editors on a Thursday afternoon. Anonymous traffic was fine. The cause was a sessions table that had outgrown ibdata1. -
TLS handover checklist: cert, chain, cron, four hidden traps
A one-page TLS handover checklist for inherited sites. The leaf, the chain, the cron and the four hidden traps hosts forget to mention at handover. -
Magento 2 reindex pinned MySQL to 100%: a stale flat row
A Magento 2 catalog reindex pinned MySQL at 100% CPU at 07:14 on a Tuesday. The culprit was 47 orphan rows in catalog_product_flat_1. Here is the trace. -
wp_users SQL: three queries that show who actually logs in
412 rows in wp_users does not mean 412 people log in. Three SQL queries against wp_usermeta turn the dump into a real picture of active, stale, and dangerous accounts. -
phpMyAdmin behind .htpasswd: a safe copy-paste recipe
You already protect phpMyAdmin with its own login. A Basic Auth gate in front means bots never touch the PHP. Here is the recipe, and the four ways it locks you out. -
LiteSpeed vs Apache vs Nginx: caching a tired WooCommerce store
A six-year-old WooCommerce store, 14k products, one VPS. We swapped the reverse cache three times and watched what actually moved the TTFB needle. -
Retainer invoice template: six lines for legacy sites
A small Utrecht studio replaced its two-page service agreement with six lines on the invoice itself. The template, and the arguments it stops. -
Drupal SFTP lockout: a 90-minute recovery and a fail2ban rule
A brute-force run locked the only admin out of a Drupal 8 site at 23:41. Here is the 90-minute recovery and the fail2ban rule that ended it. -
Joomla 3 to 4: the triage table for broken extensions
Forty-seven extensions, six fatals on the admin login, and a Friday deadline. The triage table we use to sort that mess into Update, Replace, Fork, or Rip before touching code. -
Freelancer handover: twelve minutes before they disappear
Twelve minutes is enough to find the SSH key, the orphan cron job, the file-ownership knot, and the mail alias the freelancer forgot to mention on her way out. -
Reading a php-fpm slowlog: three stack-trace shapes decoded
A field guide to php-fpm slowlogs on shared hosting: the three stack-trace shapes you'll meet, what each one is telling you, and where to point the next hour of debugging. -
Laravel migration without downtime: booking engine rebuild
A 14-year-old custom PHP booking engine, a calendar that processed a reservation every 90 seconds, and a Laravel rebuild that had to land without a single missed booking. -
.htaccess header block: a security cheatsheet for handover
A copy-paste .htaccess block of security headers, file locks and PHP-in-uploads kills. Paste once, sleep better, hand the keys back without a knot in your stomach. -
Magento 2 cache warmer: a bash playbook that survives reboots
It is 21:14 on a Friday, you ran cache:flush, and the next visitor waits four seconds. Here is the two-hour bash warmer a Dutch agency now ships on every store. -
WordPress multisite uploads leak: tracing a stale blogs.dir symlink
A WordPress multisite started serving tenant B's PDFs from tenant A's URLs. The cause was a symlink older than the agency. Here's the trace. -
ChatGPT in a 2015 Joomla admin: the smallest safe shape
A client wants an AI writing assistant inside their Joomla 3 admin. Here is the smallest safe shape we have found, and the parts we refuse to ship. -
Apache vs Nginx headers: a field guide for legacy hosting
The response headers on a legacy site tell you whether Apache or Nginx is running, who is caching the page, and what is silently rewriting your responses. Here is how to read them. -
The five-minute pre-quote audit: ls, curl, and SQL counts
The fixed-price quote that became a three-week migration usually starts with skipping a five-minute audit. Here is the exact checklist we run before naming a number on legacy work. -
Custom PHP CMS to WordPress shell: a four-week rebuild
A Dutch agency had four weeks to move a 2009 custom PHP CMS to WordPress, keep 14,000 URLs intact, and avoid touching the legacy database. Here's how. -
SFTP backups for legacy sites: a rotating snapshot recipe
Shared hosts often kill mysqldump and SSH but leave SFTP wide open. Here is a rotating snapshot recipe that still gives you a clean weekly restore point. -
Joomla 3 saves 500ing: the missing #__assets row, found in 40 minutes
A Joomla 3 site started throwing 500s the moment an editor hit Save. The cause was a single missing row in #__assets. Here is the trace, with SQL. -
SQL audit snippets for legacy WordPress: a cheatsheet
The wp_options table is 480MB. wp-admin takes nine seconds. You inherited the project last Tuesday. The eight queries we run before anything else. -
Reading a Magento 2 di.xml: the four nodes that matter
A class in your Magento 2 store behaves nothing like the vendor source. The answer is in di.xml. Four nodes do the real work; the rest is noise. -
PHP 7.4 to 8.2 upgrade: a six-step compatibility pass
A real PHP 7.4 to 8.2 migration: 60,000 lines, four authors, no test suite. The six-step pass that catches deprecations, type errors, and silent breakage. -
Drupal 7 behind Cloudflare: don't break admin or images
Flip the orange cloud on a Drupal 7 site and two things break first: the editor login loops, and image styles 404. Here's the settings.php and cache-rule playbook that holds. -
Splitting a 7GB wp_options table: an autoload field guide
A wp_options table that grew to 7GB doesn't crash WordPress, it just makes every request feel underwater. Here's how to find the autoloaded rows behind it. -
WooCommerce double charges: tracing a duplicated payment_complete hook
A WooCommerce store charges every card twice between 14:00 and 16:00. The culprit was not Stripe. It was a payment_complete hook firing from two places at once. -
Migrating 600 PHP pages to WordPress without broken URLs
A 22-person Dutch agency moved 612 URLs of custom PHP onto WordPress in five weeks. No dead links. The crawl, the rewrites, the MySQL port, and what broke anyway. -
Decoupling a Joomla template: a staged extraction playbook
A 12-year-old custom Joomla template had grown into a second CMS welded onto the first. Here is the staged extraction that pulled it back apart. -
PHP-FPM pool tuning: six settings behind the 40-user wall
Your traffic doubles and the site goes white. PHP-FPM workers are pinned at five, the queue is full, Apache is timing out. Here is the cheatsheet. -
Magento order emails: the stuck queue_message_status row
A Dutch agency calls at 23:41: their Magento store has not sent an order confirmation in three days. The trail leads to one row in queue_message_status. -
Reading EXPLAIN on a WooCommerce query: a field guide
A WooCommerce product query crawls. The EXPLAIN plan has three columns that name the actual problem: type, rows, and Extra. Read them in order. -
Drupal 7 end-of-life triage: the ten-minute audit checklist
You inherit a Drupal 7 site at 23:41 on a Sunday. The client wants a rebuild quote on Monday. Here is what to actually look at before you reply. -
Staging over SFTP only: the rsync-and-rewrite playbook
A working staging environment for legacy sites on bargain hosts that give you SFTP and nothing else. No shell, no git, no Docker. Just rsync, a search-replace, and a locked-down subdir. -
chmod -R 777 recovery: restoring a legacy site's permissions
A panicked chmod -R 777 felt like the fastest fix at 23:41. Twelve minutes later PHP-FPM was rejecting every script and the login screen returned 500s. -
Scoping AI features on a 2014 WordPress site: a sober method
A client asks for 'just a small AI feature' on a 2014 WordPress site. Here is the scoping method that keeps you from quoting blind on a PHP 5.6 codebase. -
wp_postmeta pruning: clearing 4.2M rows without ACF damage
A Dutch agency's wp_postmeta crossed 4.2 million rows and the WordPress admin started timing out. Here is the prune we ran without snapping ACF field references. -
Transactional email migration: keep the from-address
Your host is retiring shared SMTP in eighteen days and the from-address on three legacy sites cannot change. The playbook we ran on a Dutch agency's stack. -
Custom PHP framework or spaghetti: the five-question test
You opened the repo, saw a folder called lib, and found a 4,200-line file called functions.php. Before you quote a rewrite, run these five questions. -
wp_posts cleanup: removing 600 dead CPT rows safely
A staging copy, a 600-row CPT graveyard, and the four foreign-key trapdoors that turn a quick DELETE into a week of broken admin screens. -
Magento audit: 40-minute checklist for an inherited store
A 22-person agency just took over a Magento 2.3 store. The previous dev is unreachable. Here is the 40-minute audit we run before changing a single line. -
Mod_security false positives: whitelisting without floodgates
WAF blocks the WordPress save button at 16:12 on a Wednesday. The fix isn't SecRuleEngine Off; it's a tight whitelist, rule by rule, route by route, that holds. -
phpMyAdmin in 2026: the three settings that keep it safe
Half the senior engineers we work with want phpMyAdmin gone in 2026. The other half use it weekly. The three settings that resolve the argument. -
Auditing checkout JavaScript: the 15-minute network-tab pass
Friday afternoon, you open DevTools on a checkout that nobody has touched in two years and find 38 third-party scripts. Here is the fifteen-minute pass that tells you which ones can go. -
SFTP-only deployment: a recipe that outlives the developer
A reversible SFTP deploy method designed so the next person inheriting the credentials can rebuild the entire release flow from the server itself. -
Drupal 9 read-only at 16:08: a /tmp inode war story
A Drupal 9 site went quietly read-only on a Tuesday afternoon. The disk was 39 percent full. /tmp had bytes to spare. Every single inode, though, was gone. -
Reading slow_query_log on shared hosting: four patterns
Four MySQL slow_query_log patterns that explain most of the pain on shared hosting. The EXPLAIN you will see, the index that fixes it, and the ones you can leave alone. -
Magento 1.9 to Magento 2: a six-weekend live cutover
A Dutch agency had to move a Magento 1.9 store onto 2.4.7 without ever freezing the catalog. This is the six-weekend rebuild plan that actually held. -
Slow admin-ajax.php: tracing 30 seconds back to one plugin
A WooCommerce backend hung for 30 seconds on every page load. The slow request was always admin-ajax.php. Here is how we traced it to one Heartbeat plugin. -
Joomla 2.5 component to Joomla 5 plugin: skip the rewrite
An old Joomla 2.5 component still runs the client's catalogue. PHP 8.2 is coming. Here's how to lift it onto Joomla 5 as a plugin without touching the schema. -
Quoting legacy work: a four-tier scope letter that holds
The four-tier scope letter we send before touching anyone's legacy site. Quote the surface, ringfence the swamp, and keep the client honest about what they're actually asking for. -
Finding the rogue cron filling /tmp: a 12 GB session leak
The alert came in at 3:14 on a Tuesday. /tmp on a small VPS had crossed 12 GB of orphaned PHP session files. Here is how we found the cron behind it. -
Reading phpinfo() output: a topographic map of the host
Drop a phpinfo.php in the docroot and the wall of beige tables you get back is a topographic map of the host. Here is how to read it section by section. -
Magento CSV import without downtime: a 9,000-row swap
A 23:41 Loom, a 9,043-row supplier CSV, and a storefront that could not go down. The post-mortem on how a Dutch agency staged the import without losing a minute of uptime. -
The two-monitor habit: keeping FTP and MySQL in one glance
On a legacy PHP site, the bug almost always sits in the seam between the filesystem and the database. Here is why we never let one out of sight of the other. -
Eleven .htaccess directives worth keeping, three to drop
A cheatsheet for the .htaccess file you inherited on that legacy WordPress or Magento site: eleven directives worth keeping, and three that quietly hurt. -
Latin1 to utf8mb4 on legacy Joomla: a no-mojibake migration
A Dutch agency we work with inherited a 2011 Joomla site. The articles looked fine until somebody searched for an accented name. Here is the routine we used to fix it without mojibake. -
MySQL collation mismatches: a field guide to empty results
Your search returns nothing. The row is right there in phpMyAdmin. Welcome to MySQL collation mismatches, where two strings disagree about what equal means. -
14 PHP includes per page: a dependency-mapping playbook
Fourteen require statements at the top of every page, a sidebar that pulls in three more, and nobody on the team remembers why. Map the graph before you touch it. -
Drupal 7 pharma hack: tracing an old uploads injection
The customer's Google snippet read 'Buy Cialis 20mg' but the page itself looked clean. Tracing the Drupal 7 pharma hack took us through htaccess cloaking and a forgotten CV upload. -
Diffing two SFTP trees: when 'nothing changed' isn't true
A Saturday-morning ticket. The client swears nothing changed since Friday. Three shell commands that find the one file that actually did, and the diff to paste back. -
wp-login.php brute force: the .htaccess block that works
The bots have been hammering wp-login.php every two seconds for three days. Here is the .htaccess block that stops them and the reason it does not break the dashboard. -
AI-assisted SQL: when to trust the suggested JOIN clause
AI will hand you a six-table JOIN in two seconds. Half of them are right. Here is how to tell which half before you press run on production. -
9GB site restore: a partial dump and three FTP shards
An afternoon spent stitching a 9GB WordPress site back together from one truncated mysqldump and three half-broken FTP backups. Here is the restore log. -
Legacy maintenance pricing: surviving the 'tiny fix' trap
The third 'just one tiny fix' on a 2017 WordPress site is where freelancers quietly go broke. A retainer model that bills risk, not keystrokes. -
Database as source of truth: refactor the data, not the code
You inherit a 14-year-old WordPress site. You pull the repo, grep for the broken behaviour, find nothing. The real logic is a 4 KB row in wp_options. -
Fake plugin updates in wp-content: seven signatures to audit
A practical audit for spotting fake plugin updates and supply-chain malware inside wp-content. Seven signatures, with the grep and SQL to actually run. -
Magento 2 indexer stuck: recovery without losing orders
A stuck Magento 2 indexer turns checkout into a minefield: wrong prices, missing products, ghost stock. Here is the recovery sequence that protects the orders already in flight. -
Reading PHP error logs like bloodwork: a triage field guide
A 16:47 Friday message: site is throwing 500s, sometimes. You SSH in, tail the log, see eight hundred lines of red. This is how to triage them in order. -
40GB wp_options autoload: the night we hit 800ms TTFB
A Loom link at 23:41, a homepage TTFB over eight seconds, and a wp_options table that had quietly grown to 40GB. The fix was three SQL statements and a long night. -
Recovering a corrupted MyISAM table: incident walkthrough
A WooCommerce wp_options table crashed on a shared host at 23:41. Ninety minutes, no SSH, only phpMyAdmin and FTP. Here's the incident in order. -
WooCommerce checkout audit: hunting the 2.3s plugin
A Loom from a Dutch agency: WooCommerce checkout hanging three seconds on every postcode change. Here is the twenty-minute audit that finds the plugin. -
Self-hosting client work: a small studio's case for owning the dock
A three-person studio runs eleven legacy sites. The argument for self-hosting the tooling around that work, instead of renting it, gets stronger every quarter. -
Reading inherited .htaccess files: a fundamentals guide
Inherited a site whose .htaccess starts with three commented blocks of dead RewriteRules in Dutch? Read it block by block. This is the order we use. -
PHP 5.6 to PHP 8.2 in one weekend: the realistic version
The Loom landed at 23:41 on a Friday. Forty-eight hours to take a 14-year-old PHP codebase from 5.6 to 8.2. Here is the actual order it broke in. -
Retiring jQuery from a legacy theme: a working playbook
Most legacy themes still ship jQuery for two lines of code: a hamburger toggle and a smooth scroll. Here is how we retire it without spending a Saturday on rollbacks. -
AI-suggested edits: a four-step check before you apply
The chat returns a one-line SQL diff you didn't write and only half recognize. Before you apply it on a live site, run it through four steps. -
Custom PHP intranet migration: off Windows 2008 in a weekend
An undocumented custom PHP intranet, a Windows 2008 box being pulled Monday, and one weekend. Here is the inventory, the breakages, and the cutover. -
Joomla 3 to Joomla 5: template overrides that survive
Most Joomla 3 overrides are sixty percent salvageable. The other forty percent will silently break your site. Here is the rescue order we use on real client sites. -
CMS identification by filesystem: a short field guide
You inherit an FTP login, no documentation, and 20 minutes before the client call. The five places to look that name a CMS before you open a single PHP file. -
Cron jobs over FTP: auditing a server you just inherited
A site lands in your lap with an FTP credential and a database dump. Somewhere in there, jobs are firing on a schedule. You need to know which ones, before one breaks at 03:00 on Sunday. -
Legacy site manifests: a 20-minute audit blueprint
You inherit a 9-year-old WordPress site at 4pm Friday. By 4:20 you should have a manifest that tells the next person, or you in six months, what's actually running. -
Reviving a dead WordPress site: a 90-minute playbook
A Dutch agency owner sent a Loom at 23:41: white screen, no logs, no SSH. Ninety minutes later the cart was live. Here is the order we work in. -
Drupal session token leaks: three patterns and the fixes
An admin getting silently logged out is rarely a flaky cookie. On legacy Drupal sites it usually means the session token is being copied out of a referrer log or a cached page. -
phpMyAdmin alternatives in 2026: when a native client wins
phpMyAdmin still ships with every cPanel on earth. That doesn't make it the right place to run an UPDATE against 180,000 wp_postmeta rows at five past eleven on a Friday. -
Spotting a hacked WordPress install: a 5-minute audit
You have FTP, the database password, and maybe five minutes before the client wakes up. Here is the audit that gets you to a verdict, in roughly that order. -
WordPress without the dashboard: why some agencies live in the FTP
A look at why working WordPress agencies bypass wp-admin entirely and run their day from a file tree, a SQL prompt and a tail on the error log. -
Reading raw .sql dumps without importing: a CLI playbook
A 3 GB backup lands in Slack and the client wants to know if one row is in there. The CLI workflow for reading raw .sql dumps without importing them. -
Backups that aren't really backups: a seven-point audit
An agency lead sent a Loom at 23:41: the restore file was 11 MB, the database is 4.7 GB. Here's how that happens, and how to make sure it doesn't to you. -
Inline CSS and embedded fonts: 2010s habits that aged well
Eleven years on, the parts of a 2014 WordPress theme still doing useful work are the ones we used to mock: inline CSS, self-hosted fonts, plain JavaScript. -
Shared cPanel clients: the four kinds and what they need
Most agency portfolios still have a shared cPanel tail. Four kinds of clients live on it, and spotting which one is in front of you shortens the rest of the conversation. -
Legacy PHP site snapshot: a one-afternoon playbook
You just got handed SFTP creds, a vague Slack thread, and a site nobody has touched in four years. Here is how to know what you are looking at by 5pm. -
Composer and the legacy PHP site you just inherited
You inherited a WordPress install with three plugins, three vendored Guzzles, and zero composer.json. Composer init is the wrong first move. Here is the audit that comes first. -
Magento 1 in 2026: editing live without nuking the catalog cache
A Dutch agency, a frozen Magento 1 shop, a Friday at 23:41, and the catalog_product_flat tables that nearly took the storefront down at checkout time. -
Public web root: seven file types you should never expose
A WordPress install live since 2014 will tell you everything about itself if you know which seven files to ask for. None of them should be there. -
AI-assisted PHP review: what it catches, what it misses
An honest look at what AI code review actually changes when you're refactoring 14-year-old PHP: the bugs it finds in seconds, the legacy decisions it can't read. -
Front-controller routing: why /contact is not contact.php
The contact page on a 12-year-old WordPress site shows a 500 error. The first instinct is to open contact.php in FTP. That file does not exist, and it never did. -
Drupal 7 end-of-life: a checklist for clients still on it
Drupal 7 went unsupported in January 2025. The sites still humming along on it in mid-2026 are not unsupported by accident, they are unsupported by neglect. -
WordPress MySQL queries: a legacy maintainer's cheatsheet
The half-dozen SQL statements that fix legacy WordPress sites at 17:40 on a Tuesday: URL pair, autoload audit, revisions purge, password reset, transient sweep. -
FTP is not dead: when SFTP is overkill and FTPS is just right
A Friday hotfix on a Magento 1 site, no SSH, no port 22 open, and the question gets suddenly real. SFTP is not always the answer, and FTPS is not the compromise it gets framed as. -
wp-config.php on production: a safe edit procedure
wp-config.php is one line away from a white screen. Here is the procedure we use to change it on a live WordPress site without ever dropping a request. -
Joomla migration without SEO loss: a 12-year-old case study
A 12-year-old Joomla 2.5 site, three SEF plugins deep, moving to PHP 8.2 hosting. Here's how the redirects, the database, and the rankings survived the week. -
Legacy PHP reinfection: the .htaccess wall that holds
A client's WordPress kept getting reinfected every few days. Cleaning files wasn't enough. Here's the .htaccess pattern that finally stopped it. -
Legacy WordPress .htaccess: the rules that fix 80%
The .htaccess rules that quietly resolve most of the redirect loops, mixed content and broken pretty-permalinks on inherited WordPress sites.