PHP Security Tips and Best Practices
PHP is easy to learn, and that is why many beginners start with it. But the same easy way also makes it simple to write insecure code. A small mistake in a form or a database query can let someone read your data, steal accounts or even take control of the website.
In this chapter you will learn 15 practical PHP security tips. Each tip is explained in simple words, and the important ones have short code that you can copy.
Quick checklist
| # | Tip | Protects from |
|---|---|---|
| 1 | Keep PHP and software updated | Known bugs and exploits |
| 2 | Never trust user input | Bad or harmful data |
| 3 | Use prepared statements | SQL injection |
| 4 | Escape output | Cross site scripting (XSS) |
| 5 | Use CSRF tokens | Fake form submissions |
| 6 | Force strong passwords | Guessing attacks |
| 7 | Hash passwords | Stolen database |
| 8 | Protect sessions | Account hijacking |
| 9 | Upload files securely | Malicious files |
| 10 | Keep secrets out of public files | Leaked passwords and keys |
| 11 | Hide errors from visitors | Information leak |
| 12 | Use HTTPS | Data stolen on the network |
| 13 | No sensitive data in URL or headers | Information leak |
| 14 | Limit repeated requests | Brute force, bots |
| 15 | Log important events | Not knowing about attacks |
Basic protection
1. Keep PHP and software updated
Every PHP version gets security support for a limited time. After that, bugs are not fixed anymore. So use a PHP version that is still supported (check the list on php.net) and update your CMS, plugins and libraries also.
If you use WordPress or any framework, update it regularly. Most hacked websites are running an old version with a known problem.
Handling user input
2. Never trust user input
Anything that comes from a visitor is user input: forms, URL parameters, cookies, uploaded files and even some headers. Always assume it can be wrong or harmful. Check it before you use it.
PHP gives a very useful function for this, filter_var(). Here we check an email and an age.
<?php
$email = "robin@example.com";
$age = "25";
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "Email is valid<br>";
} else {
echo "Email is not valid<br>";
}
$ageInt = filter_var($age, FILTER_VALIDATE_INT, ["options" => ["min_range" => 1, "max_range" => 120]]);
if ($ageInt !== false) {
echo "Age is valid";
} else {
echo "Age is not valid";
}
?>
Output:
Email is valid
Age is valid
Validate on the server side always. Checks in JavaScript are only for convenience, because a user can skip them easily.
3. Prevent SQL injection
SQL injection happens when user input is added directly in a SQL query. The attacker can change your query and read or delete data. The fix is simple: use prepared statements and never join variables in the SQL text.
<?php
// Safe: the value is sent separately from the SQL
$stmt = $conn->prepare("SELECT id, name FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
?>
We explained this fully with examples in PHP MySQL Prepared Statements.
4. Prevent cross site scripting (XSS)
XSS happens when a visitor can put JavaScript code in your page, for example inside a comment, and that code runs in the browser of other visitors. It can steal cookies or change the page.
To stop it, convert special characters to safe HTML before you print any user data. Use htmlspecialchars().
<?php
$comment = "<script>alert('Hacked')</script>";
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
?>
Output:
<script>alert(‘Hacked’)</script>
The browser now shows this text as it is, and it does not run it. Always set ENT_QUOTES and the UTF-8 charset like in the example.
You may also see strip_tags(), which removes HTML tags from a string. It is useful to clean simple text, but it is not enough to protect your page. Use htmlspecialchars() when you print data.
5. Use CSRF tokens in forms
CSRF (cross site request forgery) is an attack where another website makes the browser of a logged-in user send a request to your site, without the user knowing. To stop it, put a secret random token in every form and check it when the form is submitted.
Step 1: create a token and put it in the form.
<?php
session_start();
// Create a token once per session
if (empty($_SESSION["csrf_token"])) {
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
?>
<form method="post" action="update.php">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION["csrf_token"]; ?>">
<input type="text" name="email">
<button type="submit">Save</button>
</form>
Step 2: check the token in update.php before doing anything.
<?php
session_start();
$token = $_POST["csrf_token"] ?? "";
if (!hash_equals($_SESSION["csrf_token"] ?? "", $token)) {
http_response_code(403);
exit("Invalid request.");
}
// Token is valid, process the form here
?>
Learn more about form data in PHP Forms Handling.
Passwords and login
6. Force strong passwords
Weak passwords like “123456” are guessed in seconds. Set a minimum length for passwords on your registration and change password forms. Longer is better, so allow at least 8 characters, and even more if you can.
<?php
$password = "abc123";
if (strlen($password) < 8) {
echo "Password must be at least 8 characters long.";
}
?>
Output:
Password must be at least 8 characters long.
7. Hash passwords, never store them as plain text
If someone steals your database, plain text passwords are open for all. Never save the real password. Save a hash instead. Do not use md5() or sha1() for this, they are too fast and easy to break. PHP has built-in functions that do it correctly.
<?php
$password = "MySecret@123";
// Create a hash to store in the database
$hash = password_hash($password, PASSWORD_DEFAULT);
// At login: compare the typed password with the stored hash
var_dump(password_verify("MySecret@123", $hash));
var_dump(password_verify("wrong-password", $hash));
?>
Output:
bool(true)
bool(false)
8. Protect sessions
A session id is like a key of the logged-in user. If someone steals it, they can use the account. Do these things to keep it safe:
- Call
session_regenerate_id(true)right after a successful login. - Set the session cookie as secure (HTTPS only), httponly (not readable by JavaScript) and SameSite.
- Destroy the session completely on logout.
<?php
session_set_cookie_params([
"secure" => true,
"httponly" => true,
"samesite" => "Lax"
]);
session_start();
// After the user logs in successfully
session_regenerate_id(true);
?>
You can read the basics in PHP Session.
Files and configuration
9. Upload files securely
File upload is one of the most risky features. If you accept any file, someone can upload a PHP file and run it on your server. Follow these rules: check the real file type, limit the size, rename the file, and store it outside the public folder if you can.
<?php
if ($_FILES["photo"]["error"] !== UPLOAD_ERR_OK) {
exit("Upload failed.");
}
// Allowed types and the extension we will use for each
$allowed = ["image/jpeg" => "jpg", "image/png" => "png"];
// Check the real type of the file, not the name given by the user
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES["photo"]["tmp_name"]);
if (!isset($allowed[$mime])) {
exit("Only JPG and PNG files are allowed.");
}
if ($_FILES["photo"]["size"] > 2 * 1024 * 1024) {
exit("File is too big. Maximum 2 MB.");
}
// Make a random safe file name, never use the original name
$newName = bin2hex(random_bytes(16)) . "." . $allowed[$mime];
move_uploaded_file($_FILES["photo"]["tmp_name"], "/var/www/uploads/" . $newName);
?>
Basics of uploading are in PHP File Upload.
10. Keep secrets out of public files
Database passwords, API keys and other secrets should not be in files that visitors can open. Keep the config file outside the public folder (above it), and do not upload it to Git.
<?php
// config.php is stored one level above the public folder
// It returns an array of settings
return [
"db_host" => "localhost",
"db_user" => "app_user",
"db_pass" => "change-this-password"
];
?>
<?php
// index.php, inside the public folder
$config = require __DIR__ . "/../config.php";
?>
If you cannot move the file out of the public folder, block direct access to it. For Apache servers you can see how in Htaccess Deny Access.
11. Hide errors from visitors
Error messages can show file paths, database names and even passwords to attackers. On a live website, do not display errors. Save them in a log file instead and read them yourself.
<?php
// Production settings
ini_set("display_errors", "0"); // do not show errors to visitors
ini_set("log_errors", "1"); // save errors in a log file
error_reporting(E_ALL); // still report everything
?>
Showing errors is fine on your local computer while you are learning and debugging. Read more in PHP Errors.
Server and network
12. Use HTTPS
Without HTTPS, passwords and form data travel as plain text and anyone on the same network can read them. Install an SSL certificate (many hosts give it free), and redirect all http requests to https. Here is how to do it on Apache: Htaccess Redirect Http to Https.
13. Do not send sensitive data in URL or headers
Everything in the URL is saved in browser history, server logs and sometimes in other websites (as referrer). So never put passwords, tokens or personal data in a GET parameter. Send them with POST over HTTPS.
Also hide details that help an attacker. For example, remove the PHP version from the response headers.
<?php
// Remove the X-Powered-By header that shows the PHP version
header_remove("X-Powered-By");
?>
You can also set expose_php = Off in php.ini.
14. Limit repeated requests
Bots try thousands of passwords on a login page, or send too many requests to slow down your site. Some ways to reduce the problem:
- Lock the account or add a delay after 5 failed login attempts.
- Use a CAPTCHA on login, register and contact forms.
- Use a firewall or a service like Cloudflare, or tools like Fail2ban on the server, to block IP addresses with too many requests.
15. Log important events
If something goes wrong, logs are the only way to know what happened. Save failed logins, password changes, and other important actions. But never write passwords or tokens in the log.
<?php
$username = "robin";
// Remove new lines so nobody can fake extra log lines
$safeName = str_replace(["\r", "\n"], "", $username);
error_log("Failed login attempt for user: " . $safeName);
?>
Result: The message is saved in the PHP error log file of your server.
Summary
- Update PHP, CMS and plugins regularly.
- Validate all input on the server, and escape all output with htmlspecialchars().
- Use prepared statements for every query that has user data.
- Protect forms with CSRF tokens and protect sessions with secure cookies.
- Hash passwords with password_hash() and check them with password_verify().
- Check uploaded files, keep secrets outside the public folder and hide errors on live sites.
- Use HTTPS, limit repeated requests and keep logs of important events.
| Exercises & Assignments |
|---|
| No Content Found. |
| Interview Questions & Answers |
|---|
| No Content Found. |