I have been writing PHP, on and off, for longer than I would like to admit. For most of that time, I have been faintly defensive about it in front of other security people, because the language has carried a reputation that — even when it was deserved — has lasted longer than the conditions that produced it. The reputation is now an artefact of an earlier era. Modern PHP is a perfectly defensible runtime, and a modern PHP application can be operated to a security standard that holds its own against any equivalent stack.
Where the reputation came from
The reputation came from a particular era of PHP — register globals, magic quotes, weakly-typed string handling, frameworks that encouraged interpolated SQL — and from the fact that PHP was, for some years, the dominant language for amateur web development. The language was easy to start with, the documentation was plentiful, and the result was a long tail of applications written by people who were not (and never claimed to be) security engineers. The bad ones got famous. The discipline-led ones did not, because nobody writes news stories about the PHP application that did not get breached.
Most of the structural problems are gone. Register globals were retired in PHP 5.4. Magic quotes followed. The language has had typed properties since 7.4 and increasingly strict type enforcement since 8. The standard library's password hashing has been Argon2id-capable since 7.2. The crypto extensions are sensible. The dependency manager, Composer, is now a first-class citizen, with a security-audit subcommand and a thriving ecosystem of audit tooling.
The disciplines that matter
I will lay out the disciplines I apply to every PHP application I build or review, with the brief reasoning. None of this is novel. Novelty is not the point — the point is that it is consistently applied.
PDO with prepared statements, always. The single most common SQL-injection vector in modern PHP applications is not an unsafe API; it is the use of sprintf, "." concatenation, or template interpolation to build queries because the developer was tired. PDO with bound parameters is the default; nothing else is acceptable. A code-review rule that flags any string-built SQL costs nothing and prevents most of the family of vulnerabilities.
Argon2id passwords with a server-side pepper. password_hash with PASSWORD_ARGON2ID is the right default. The pepper — a server-side secret mixed into the input before hashing — adds a useful defence against database-only compromise, because a stolen password table is no longer attackable without also stealing the pepper. The pepper lives in the configuration, not in the code, not in the database.
CSRF tokens on every state-changing form. The token rotates on validation. The framework code makes it impossible to accept a state-changing POST without the token. There is no opt-out for this is just an internal form; the cost of consistency is much lower than the cost of the one form somebody forgot.
Sessions with the right cookie flags. HttpOnly, Secure, SameSite=Strict. Session ID rotated on login and periodically thereafter. Session fingerprint bound to user-agent at minimum. Session storage somewhere that supports atomic operations — ideally Redis with setex, not the file system, particularly not on a multi-server estate.
Strict Content Security Policy. Inline scripts forbidden. Allowed sources enumerated explicitly. Reports captured to an endpoint that someone reads. Most XSS in modern PHP applications is small, residual, and would be neutered by a CSP that disallowed inline scripts; the cost of writing one well is a single afternoon of work, and the value compounds for years.
Output escaping at the template boundary, explicitly. Templating frameworks that auto-escape are convenient and safe by default; templating frameworks where you opt in to escaping are an accident waiting to happen. If you are using a hand-rolled template approach, write the helper, use it everywhere, and reject the PR that uses raw concatenation.
Dependency management. composer audit in CI. Renovate or Dependabot enabled. A standing rule that no dependency is added without someone reading the lockfile diff and the upstream changelog. Most modern compromises that come through PHP applications now come through dependencies, not through application code; the discipline tax is on the dependency layer.
The middleware that holds it together
The disciplines above operate inside an application. The application operates inside a stack, and the stack has its own security posture. PHP-FPM with expose_php = Off, sensible disable_functions, opcache with validate_timestamps = 0 for production, and an Apache or nginx vhost with the security headers (HSTS, CSP, X-Content-Type-Options, X-Frame-Options, Permissions-Policy, Cross-Origin-Opener-Policy and friends) configured at the vhost layer rather than only in the application. The vhost is the last word; the application can ask for headers, but only the vhost can guarantee they are present on every response.
Rate limiting on authentication endpoints is non-negotiable. Lockout policy on repeated failures, per-IP and per-username, ideally backed by Redis so that a multi-host deployment shares the counter rather than each host having its own. Two-factor authentication, time-based one-time password is fine, on anything that meaningfully matters.
Logging that does not betray you
It is easy to log things that should not be logged: passwords accidentally captured in request dumps, session IDs leaking into stack traces, secrets passing through error reports. The discipline is to centralise logging through a small library, redact aggressively, and audit what is being captured. The other half of the discipline is to make sure that what is captured is useful — failed logins with their IPs, configuration changes with the actor, error contexts that survive a forensic review.
Centralised log shipping, off-host, with retention that meets the forensic horizon. Logs the application can write but cannot delete. Log integrity matters more than people think it does until they are doing the post-incident timeline.
The honest limit
PHP is not the worst platform to defend, and it is not the best. It is a platform on which a thoughtful team can build a defensible application, and on which a careless team can build a disaster — which is true of every platform. The advice I give to teams considering PHP for a new project in 2026 is the same advice I would give for any other server-side language: pick the platform on technical fit, pick the discipline regardless. The first decision is interesting; the second is the one that determines the outcome.
I will say one thing in PHP's specific favour: the boring middleware story is good. Mature templating, mature ORMs, mature auth libraries, mature deployment patterns, and a runtime that behaves predictably under load. The unglamorous bits, in my experience, are where modern web platforms quietly differentiate themselves, and PHP's unglamorous bits are in better shape than its public reputation would suggest.
If your reflex when you see a PHP project is to assume it is insecure, update your prior. Then read the code.
Related reading
If this piece was useful, the most directly adjacent posts on the site are:
- Hardened Linux: the boring middleware nobody writes about
- Security architecture for the operationally honest
- Threat modelling that survives contact with delivery
The skills page groups all ten companion articles by area of practice, and the experience page covers the engagements that the practice was shaped by.