Use this list before you launch, and again every few months. Each item links to the lesson that explains it.
Server and PHP
- [ ] A supported PHP version (see PHP Versions), with updates applied.
- [ ] HTTPS everywhere, with HTTP redirected to it.
- [ ]
display_errors = Off,log_errors = On(Production Error Handling). - [ ]
expose_php = Off; unused extensions disabled. - [ ]
allow_url_include = Off(Include Security). - [ ] Only
public/is reachable. Code, config, logs and uploads are outside it. - [ ] Directory listing off; dotfiles,
.git, backups andphpinfo()pages not served.
Code and dependencies
- [ ]
composer auditshows no known vulnerabilities; dependencies are current (Updating Packages). - [ ] No secrets in Git; credentials come from the environment (Sensitive Data).
- [ ]
declare(strict_types=1)and types on new code.
Input and output
- [ ] Prepared statements for every database value; allow-lists for column names (SQL Injection).
- [ ] Input validated by type, length, range and allowed values (Input Validation).
- [ ] Output escaped for its context (Output Escaping, XSS).
- [ ] No
includeof paths built from user input. - [ ] No
eval(), nounserialize()on untrusted data, no shell commands with user input.
Authentication and sessions
- [ ] Passwords hashed with
password_hash()(Password Security). - [ ] Login rate limiting and a generic error message (Authentication Security).
- [ ]
session_regenerate_id(true)at login (Session Fixation). - [ ] Cookies:
Secure,HttpOnly,SameSite(Secure Cookies). - [ ] Idle and absolute session timeouts; a full logout (Secure Sessions).
- [ ] CSRF tokens on every state-changing form (CSRF).
- [ ] Every protected page and action checks the user and their rights on the server (Protecting Pages).
Files
- [ ] Uploads checked by real content type, size, random names, stored safely (Secure File Uploads).
- [ ] The uploads folder cannot run PHP.
Headers
- [ ]
X-Content-Type-Options,Referrer-Policy, a CSP, HSTS, framing rules (Security Headers).
Database
- [ ] A dedicated database user with least privilege, not
root. - [ ] The database is not reachable from the internet.
- [ ] Regular, tested backups, stored off the server.
Monitoring
- [ ] Errors and security events are logged, without secrets (Error Logging).
- [ ] Someone actually reads the logs, or alerts exist.
- [ ] You know how to rotate a secret and restore a backup.
A tiny self-check script
<?php
function checkSettings(array $ini): array
{
$problems = [];
if (($ini["display_errors"] ?? "0") !== "0") {
$problems[] = "display_errors should be off in production";
}
if (($ini["expose_php"] ?? "0") !== "0") {
$problems[] = "expose_php should be off";
}
if (($ini["allow_url_include"] ?? "0") !== "0") {
$problems[] = "allow_url_include should be off";
}
return $problems;
}
print_r(checkSettings(["display_errors" => "1", "expose_php" => "0", "allow_url_include" => "0"]));
Array
(
[0] => display_errors should be off in production
)
In real code you would pass ini_get(...) values. It is a starting point for your own deployment checks.
Security is a habit
No checklist makes a site “secure” forever. New bugs, new attacks and new dependencies appear all the time. The habits that matter are: trust nothing from outside, escape on output, use the safe tool for each job, keep things updated, and log what happens.