Making a Static Site Agent-Ready: What I Changed, What I Didn't, and Why

Tested on: Hugo 0.147.0 (extended), Cloudflare Pages (Free plan), 23 May 2026. The standards in this space are moving fast — re-check the IANA Link Relations registry and the IETF Content-Signal draft before copying anything verbatim into a production site. The phrase “agent-ready” covers a moving target. Twelve months ago it meant “have a sitemap.” Today it touches RFC 8288 Link headers, the IETF Content-Signal draft, IANA registered relations, content negotiation for text/markdown, llms.txt, AI sitemaps, and /.well-known/agent-skills/. Most of those are drafts or vendor experiments. Some of them are stable enough to ship now. Others are worth watching but not worth wiring up yet. ...

Last updated:  · 11 min · Paul Masterson

Deploying a Flask Tools Site on a Hardened VPS Behind Caddy

Tested on: Ubuntu 24.04 LTS, Python 3.12.3, Caddy 2.x, gunicorn 23.0, Flask 3.x, SQLite as the data store. The pattern is portable to any small Python web app — drop in whichever Flask / Starlette / FastAPI codebase you have. Why this matters Most Flask deployment tutorials end at flask run --host 0.0.0.0 and a hand-wave about Nginx. That gap — between “the app starts” and “the app is a service my colleagues would trust at 2am” — is where the operationally interesting decisions live: which user runs it, how it talks to the reverse proxy, where its secrets live, how it survives a reboot, how its certificate renews itself. ...

Last updated:  · 11 min · Paul Masterson

Privacy by Design — What Every Intern Should Learn First

Who this is for: developers in their first year or two of writing production code, and the engineers and team leads mentoring them. If you operate the infrastructure rather than write the application, the companion piece is privacy-by-design-server-build. Introduction Most developers learn privacy backwards. They ship a feature; later someone in legal or security flags an issue; later still they retrofit the fix. By that point the data model has been baked in, the API contract is in production, and the rollback is more expensive than the right-thing-to-do would have been on day one. ...

Last updated:  · 8 min · Paul Masterson

WordPress Hardening for Agencies

Tested on: WordPress 6.5.x on Ubuntu 24.04 LTS, Nginx 1.26.x, PHP-FPM 8.3, MariaDB 10.11. Most settings translate directly to Apache; Nginx-specific blocks are clearly marked. Why this matters WordPress powers a large share of the web, which makes it the most attacked CMS. Most WordPress incidents are not novel — they are one of: An out-of-date plugin with a known CVE, exploited within days of public disclosure. wp-login.php brute-force, succeeding because an admin reused a password leaked from elsewhere. Filesystem permissions that let the web server write to source files, so a single PHP RCE becomes persistent. A backup containing the entire database, stored unencrypted, exposed via a misconfigured S3 bucket. For an agency hosting multiple client sites, the blast radius compounds: one compromised site can pivot to siblings via shared filesystem, shared database server, or shared PHP-FPM pool. This guide gives you a baseline that addresses each of the above and isolates per-site risk. ...

Last updated:  · 8 min · Paul Masterson

WordPress Hosting Security Checklist

Scope Applies to any production WordPress site, with extra emphasis on agency hosting (multiple client sites on shared infrastructure). Operational detail for each item lives in wordpress-hardening. WordPress sites almost always process personal data — commenters, contact-form submissions, customer accounts. GDPR applies; treat the host as in-scope. 1. Pre-build VPS baseline complete. Work through the pre-launch VPS checklist before installing WordPress. Privacy decisions made. See privacy-by-design-server-build for the design-level questions — they apply to WordPress too. 2. Server-level isolation Per-site Unix user. Each client site owns its own user, group, document root. No shared www-data. NIS2: 21(2)(e); ISO 27001: A.5.15. Per-site PHP-FPM pool. Separate socket, separate open_basedir, separate worker limits. open_basedir scoped to the site’s root. Prevents cross-site filesystem access if one site is compromised. disable_functions configured. exec, passthru, shell_exec, system, proc_open, popen, parse_ini_file, show_source disabled at minimum. expose_php = Off. 3. Filesystem permissions Source code is not web-writable. PHP-FPM has read, no write on wp-includes, wp-admin, plugin and theme directories. NIS2: 21(2)(e); ISO 27001: A.8.4. wp-config.php mode 640, group nginx (or your web group). Never world-readable. PHP execution blocked under uploads/. Nginx deny all for \.php requests under the uploads tree. .git, .env, backups not in the document root. Verified by attempting to fetch them via HTTP. xmlrpc.php blocked. Unless a documented use case requires it; in which case IP-allowlist. 4. WordPress core configuration FORCE_SSL_ADMIN = true in wp-config.php. DISALLOW_FILE_EDIT = true. No theme/plugin editor in the dashboard. DISALLOW_FILE_MODS = true. Updates happen via wp-cli, not in-dashboard. FS_METHOD = 'direct'. Required when the web user cannot write source files. Authentication salts regenerated. Unique per install, rotated after any suspected compromise. NIS2: 21(2)(h); ISO 27001: A.8.24. XML-RPC disabled at the WordPress layer too. add_filter('xmlrpc_enabled', '__return_false');. 5. Database Dedicated database user per site. Not shared across sites. Least-privilege grants only. SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES. No global grants. NIS2: 21(2)(e); ISO 27001: A.5.15. Database baseline applied. See postgresql-hardening for Postgres, or apply equivalent MariaDB/MySQL hardening. TLS between WordPress and database. Mandatory across hosts. 6. Admin access No admin username. Removed at install; per-human admin accounts only. NIS2: 21(2)(i); ISO 27001: A.5.16. Strong password policy enforced. Minimum length, no common passwords. 2FA required for every admin account. Hardware key or TOTP preferred over SMS. NIS2: 21(2)(j); ISO 27001: A.8.5. Login rate limiting active. Nginx limit_req on wp-login.php (see nginx-ratelimit) plus a plugin like Limit Login Attempts Reloaded. /wp-admin IP-allowlisted where feasible. If client admins work from known networks. 7. Plugins and themes Source vetted. Every plugin from wordpress.org or a reputable vendor, updated within six months, not in a “closed” state. NIS2: 21(2)(d) (supply chain). Premium plugins licensed and current. No nulled / pirated copies — common compromise vector. Inactive plugins removed. Inactive ≠ uninstalled. Uninstall what is not in use. Update SLA documented. Security-flagged updates within 24 hours; routine updates within 7 days. NIS2: 21(2)(e),(g). No in-dashboard file editing. Enforced by DISALLOW_FILE_EDIT and by policy. 8. Backups Daily database backup + weekly content backup scheduled. Off-site, encrypted backup target. restic / borg to EEA region. GDPR: Article 32(1)(c). Backup retention matches the privacy policy. Cannot exceed stated user-data retention. GDPR: Article 5(1)(e). Backups not in the document root. Common misconfiguration — verify by HTTP. Restoration drill performed and timed. Quarterly minimum. 9. Logging and integrity Nginx access logs use a minimised log_format. Per the privacy-by-design guide. Login attempts logged and alerted on. wp core verify-checksums scheduled and alerting. NIS2: 21(2)(g); ISO 27001: A.8.16. wp plugin verify-checksums --all scheduled. 10. GDPR-specific items Privacy notice published and accurate. Reflects what is collected, including by plugins. Cookie consent implemented for any non-essential cookies. No pre-ticked boxes. GDPR: Article 7. Right to erasure tested. WordPress core supports it; verify every plugin storing personal data respects the request. Right to access tested. Data export produces a complete and readable file. Sub-processor list maintained. Every plugin or service that sends data off-site (analytics, email, payment, CRM). GDPR: Article 28. 11. Ongoing operation Update process documented. Who runs it, when, how out-of-hours updates are handled. Quarterly re-run of this checklist. Plugin drift, retention drift, and admin-account creep are the usual findings. External vulnerability scan run. WPScan or equivalent; results documented. Notes For agency hosting at scale, every item under “Server-level isolation” is non-negotiable. One compromised site can pivot to siblings within minutes if isolation is missing — and the compounding blast radius is exactly the failure mode regulators ask about first. ...

Last updated:  · 4 min · Paul Masterson

Privacy by Design for a New Server Build

Applies to: any new Linux server intended for production where you will store, process, or transit personal data — including the metadata kind (IP addresses, user agents, login timestamps), which counts under GDPR whether or not your application has a “users” table. Why this matters GDPR Article 25 — Data protection by design and by default — is the legal text that turns “we should think about privacy” into “you must demonstrate that you thought about privacy, on paper, before you started processing.” It applies whether or not you have a website with a signup form. An Nginx access log with full client IPs is processing personal data. ...

Last updated:  · 10 min · Paul Masterson