Writing Secure PHP: 10 Practices We Use in Every Project
PHP powers a huge share of the web, which also makes it a favourite target. These are the habits that keep the systems we build out of the breach reports.
Most PHP security problems are not exotic. They are the same handful of mistakes, repeated. After building business systems, hospital platforms and APIs, these are the practices we treat as non-negotiable.
1. Always use prepared statements
Never build SQL by concatenating strings. With PDO it looks like this:
$stmt = $pdo->prepare('SELECT * FROM patients WHERE hospital_no = :no');
$stmt->execute(['no' => $hospitalNo]);
$patient = $stmt->fetch();
Prepared statements separate code from data, which removes SQL injection as a class of bug rather than a case-by-case risk.
2. Hash passwords with password_hash
Never write your own hashing, and never use md5 or sha1 for passwords.
$hash = password_hash($password, PASSWORD_DEFAULT);
password_verify($inputPassword, $hash); // boolean
PASSWORD_DEFAULT currently maps to bcrypt and will move to stronger algorithms over time. Existing hashes keep verifying, and you can rehash on login when the cost changes.
3. Escape output, not input
echo htmlspecialchars($userInput, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
Store data as it arrives; escape it where it is rendered. Escaping on input corrupts data, escaping on output prevents XSS.
4. Validate with allow-lists
Check that a value is one of the values you expect, rather than banning the ones you fear.
$allowed = ['reception', 'clinician', 'lab', 'pharmacy'];
if (!in_array($role, $allowed, true)) {
http_response_code(400);
exit('Invalid role');
}
5. Configure sessions properly
session_set_cookie_params([
'httponly' => true,
'secure' => true,
'samesite' => 'Lax',
]);
session_start();
Regenerate the session ID after login to prevent session fixation.
6. Protect forms with CSRF tokens
Every state-changing request should carry a token tied to the user's session, verified with a constant-time comparison.
7. Never expose credentials
Database passwords, API keys and tokens belong in environment variables or files outside the web root. A single .env accidentally served as text has leaked more production databases than any clever exploit.
8. Handle uploads like they are hostile
Validate MIME type and extension, store outside the web root or serve through a controller, generate random filenames, and never execute anything from an upload directory.
9. Turn error reporting off in production
Show users a friendly page; log details privately. Stack traces in production are a free map of your file system.
10. Rate-limit authentication
Limit login attempts per account and per IP, add exponential backoff, and log failures. Brute-force protection is not optional for admin panels.
None of this is complicated. It is simply disciplined. The systems we build treat these as defaults, and the maintenance conversations afterwards are much shorter because of it.