DEV Community

MP Singh
MP Singh

Posted on

I built a Drupal installer that tells you if your site is safe to ship

Most Drupal installers stop at "it's running."

Mine doesn't.


The problem nobody talks about

You follow the tutorial. You provision a server. You install Drupal. The site loads. You think you're done.

You're not.

Your trusted_host_patterns might be unconfigured — meaning any Host header gets accepted, opening you to cache poisoning. Your private file path might be unset — meaning files uploaded to private:// are publicly accessible via direct URL. Your Redis might be running but not actually wired as Drupal's cache backend. Your TLS cert might have 12 days left and you have no alert for it.

The site looks fine. The vulnerabilities are invisible.

Every Drupal developer I know has shipped something that looked fine and wasn't. Including me.


What I built

Actools is a Drupal 11 installer for Hetzner VPS. One command, complete stack — Caddy 2, PHP 8.3-FPM, MariaDB 11.4, Redis 7, XeLaTeX PDF worker, automated backups.

That part exists. There are other installers.

What's different is this:

$ actools audit
Enter fullscreen mode Exit fullscreen mode

25 checks. Four categories. A score out of 10. Fix commands attached to every finding.

=== ACTOOLS DRUPAL AUDIT ===

[DRUPAL]
  PASS  Security advisories: none found
  PASS  trusted_host_patterns: configured
  PASS  Error display: hidden
  PASS  Private file path: configured and writable

[INTEGRATION]
  PASS  Redis: write/read/TTL confirmed
  PASS  Queue worker: enqueue test passed
  PASS  HTTP: Cache-Control header present

[STACK]
  PASS  Containers: all 5/5 running
  PASS  Site response: HTTP 200
  PASS  TLS: valid, 90 days remaining
  PASS  MariaDB: reachable
  PASS  Worker container: healthy

[SECURITY]
  PASS  HTTPS: HTTP redirects to HTTPS
  PASS  HSTS header: present
  PASS  X-Frame-Options header: present
  PASS  Server header: hidden

─────────────────────────────────────────
  PASS: 22   WARN: 5   FAIL: 1
  Audit score: 6/10
  Fix FAIL items before next deploy.
Enter fullscreen mode Exit fullscreen mode

Not a dashboard. Not a Drupal module. A CLI tool that tells you the truth about your own server.


The line that started it

At the top of audit.sh there's a comment:

# The Drupal community has enough Report modules.
# What it lacks is a CLI tool that says:
# I found a problem. I won't let you deploy until you run this specific command to fix it.
Enter fullscreen mode Exit fullscreen mode

That's the whole product philosophy in three lines. Written before a single check existed.


What it checks

Drupal layer

  • Security advisories via drush pm:security
  • trusted_host_patterns — reads settings.php and verifies it's active
  • Config drift — but only if the sync directory has a baseline (fresh installs get INFO, not false WARNING)
  • Error display mode
  • Session cookie security flags
  • Queue backlog

Integration layer

  • Redis behavioral test — not just "is it running" but write/read/TTL cycle
  • Redis as actual Drupal cache backend
  • HTTP cache headers
  • Queue worker — enqueues a test job and verifies processing
  • Private file path — verifiable and writable

Stack layer

  • All containers running
  • HTTP 200 response
  • TLS validity and days remaining
  • Disk usage
  • Memory available
  • Backup existence and age
  • MariaDB reachability
  • Worker container health

Security layer

  • HTTPS redirect
  • HSTS header
  • X-Frame-Options
  • X-Content-Type-Options
  • Server header hidden
  • Referrer-Policy
  • Docker image pinning

The honest score

Fresh install scores 6/10.

Not 10. Not "everything is perfect." Six. Because on a brand new server there's no backup yet, Redis isn't wired as the cache backend by default, and a few medium-priority items need operator attention.

That's honest. A tool that gives you 10/10 on a fresh install is lying to you.

The score goes up as you fix things. Run actools backup. Wire Redis. Pin your Docker images. Each fix moves the needle.


What I learned building it

Shell escaping across Docker layers is genuinely hard.

Injecting $settings['trusted_host_patterns'] into a PHP file through bash → docker exec → bash → heredoc — every layer eats escape characters differently. I went through printf, echo -e, inline quoting, and eventually landed on the only solution that actually works:

docker compose exec -T "$php_svc" bash -c "cat > /tmp/php_inject.php << 'EOF'
\$settings['trusted_host_patterns'] = array('^${domain_escaped}\$', '^.*\\.${domain_escaped}\$');
// trusted_host_patterns_active
EOF
cat /tmp/php_inject.php >> /path/to/settings.php
rm -f /tmp/php_inject.php"
Enter fullscreen mode Exit fullscreen mode

Quoted heredoc inside the container. Write to temp file. Append. Delete. No escaping war.

Three AI systems independently converged on this exact pattern when I described the problem. That's usually a sign it's right.

Idempotency checks need to be precise.

My first idempotency check used grep -q file_private_path settings.php. It matched the commented-out default line # $settings['file_private_path'] = ''; and skipped the injection every time. The installer said "set" — nothing was actually written.

Fix: grep -q "^$settings\['file_private_path'\]" — anchor to the start of line, require the actual PHP assignment.

Cache matters more than you think.

Settings written to settings.php aren't visible to Drupal until the cache is rebuilt. The installer now runs drush cr immediately after both injections. Obvious in hindsight. Invisible until you're staring at an audit that says CRITICAL on something you just fixed.


The stack

  • Drupal 11 + PHP 8.3-FPM
  • Caddy 2 (automatic HTTPS, security headers, rate limiting)
  • MariaDB 11.4
  • Redis 7
  • XeLaTeX worker (PDF generation, self-contained)
  • GitHub Actions CI (bats + shellcheck + Trivy + CodeQL)
  • Hetzner CX22 — €10/month

MIT license. No lock-in. The installer is free. What you install today stays yours. Future modules are optional.


Try it

git clone https://github.com/actools-pl/actoolsDrupal.git
cd actoolsDrupal
cp actools.env.example actools.env && nano actools.env
sudo ./actools.sh
actools audit
Enter fullscreen mode Exit fullscreen mode

You need a Hetzner VPS running Ubuntu 24.04 and a domain pointed at it. That's it.

First tester feedback welcome at GitHub Issues.


Built with Claude. For Claude.

Top comments (0)