A LAMP application (Linux, Apache, MySQL or MariaDB, PHP) is only as safe as the code on top of it. The server can block a lot, but SQL injection and cross-site scripting live in your own PHP, and only your code can close them. This guide covers the fixes that matter most in 2026, with code you can copy.
Use prepared statements for every database query, escape every value you print with htmlspecialchars(), and hash passwords with password_hash(). Add a Content Security Policy and secure cookie flags, keep PHP, your CMS and your libraries updated, and never trust user input. On shared hosting the firewall and server config are managed for you; on your own VPS they are your job.
1. What you control and what the server controls
Security on a LAMP site is split between the server and the application.
| Layer | Shared hosting | Your own VPS |
|---|---|---|
| Operating system, Apache, PHP builds | Managed by the host | You |
| Firewall and web application firewall | Managed by the host | You |
| PHP version choice | You, in the control panel | You |
| Your code, CMS, plugins and libraries | You | You |
| Database users and passwords | You | You |
The rest of this guide is about the bottom two rows, which are always yours.
2. SQL injection: use prepared statements
SQL injection happens when user input is pasted into a query string. This code is vulnerable:
// Vulnerable: never build SQL from user input
$sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";
$result = $mysqli->query($sql);An attacker who types ' OR '1'='1 changes the meaning of the query. The fix is a prepared statement, which sends the SQL and the data separately so the data can never become code:
$pdo = new PDO(
'mysql:host=localhost;dbname=app;charset=utf8mb4',
$dbUser,
$dbPass,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false]
);
$stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE email = :email');
$stmt->execute(['email' => $_POST['email']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);Prepared statements protect values, not table or column names. If a user chooses a sort column, check it against a fixed list:
$allowed = ['name', 'created_at'];
$sort = in_array($_GET['sort'] ?? '', $allowed, true) ? $_GET['sort'] : 'name';The old mysql_* functions were removed in PHP 7. If your code still calls mysql_query(), it will not run on any supported PHP version; move it to PDO or mysqli with prepared statements. More examples are in Preventing SQL injection in PHP and Node.js.
3. Passwords: hash them, never compare plain text
The old version of this example compared the password inside the SQL query, which means passwords were stored in plain text. Store a hash instead and check it in PHP:
// When the user registers
$hash = password_hash($password, PASSWORD_DEFAULT);
// When the user logs in
if ($user && password_verify($_POST['password'], $user['password_hash'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
}PASSWORD_DEFAULT picks a strong algorithm and can change as PHP improves, so give the column room (255 characters). Call session_regenerate_id(true) after login so an attacker cannot fix a session ID in advance.
4. Cross-site scripting: escape every output
Cross-site scripting (XSS) happens when a page prints user input as HTML, so a comment containing a script runs in other visitors' browsers. Escape at the moment you print, for the place you print it:
function e(?string $value): string {
return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
echo '<p>' . e($comment) . '</p>';- For values inside JavaScript, use
json_encode($value, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT). - For values in a URL, use
rawurlencode(). - If users may submit formatted text, use a maintained HTML sanitiser library rather than your own filter.
Template engines such as Twig and Laravel's Blade escape output by default; keep it that way and avoid the "raw" output syntax for user data. See Preventing XSS in PHP and Node.js.
5. Validate input, and protect forms from CSRF
Treat every value from the browser as untrusted: form fields, query strings, cookies and headers. Validate the type and range you expect, and reject anything else:
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 13, 'max_range' => 120]]);
if ($email === false || $age === false) {
http_response_code(422);
exit('Invalid input');
}Validation is not a substitute for prepared statements or output escaping; you need all three. Also add a CSRF token to every form that changes data, so another site cannot submit it on a logged-in user's behalf:
$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
// In the form: <input type="hidden" name="csrf" value="...">
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(403);
exit;
}6. Security headers and cookie flags
Headers tell the browser to refuse whole classes of attack. Set them in PHP or in .htaccess:
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; frame-ancestors 'self'"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000"
</IfModule>frame-ancestorsin the CSP replaces the olderX-Frame-Optionsheader for clickjacking protection.- Do not add
X-XSS-Protection. Modern browsers have removed the filter it controlled, and the header is no longer recommended. - Start a new CSP in report-only mode if your site loads scripts from other domains, then tighten it.
- Only add
Strict-Transport-Securityonce HTTPS works on every page.
For session and login cookies, set Secure, HttpOnly and SameSite:
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();The full header reference is in Security headers explained: CSP and HSTS.
7. Keep everything updated and lock down the rest
Most real break-ins come through out-of-date software, not clever code attacks.
composer outdated locally and upgrade regularly.display_errors off and log errors instead..env and credentials outside public_html where possible.8. Firewalls and intrusion detection
A web application firewall such as ModSecurity blocks many common SQL injection and XSS attempts before they reach PHP. It is a safety net, not a replacement for the fixes above.
- On your own VPS you install and tune these yourself: ModSecurity with the OWASP Core Rule Set for Apache, a host firewall, and log-based blocking such as Fail2ban.
- On shared hosting you cannot change server config. The firewall and WAF are managed for the whole server.
9. Running this on Domain India
Measured on our servers on 20 September 2026, the Domain India cPanel server runs Apache with ModSecurity and Imunify360's full rule set, a CSF firewall, and CloudLinux CageFS, which isolates each account from the others. These apply server-wide, to every account on the server. Imunify360 also removes known malicious code from infected files automatically and keeps the original for 14 days. None of this fixes insecure code in your application, so sections 2 to 7 still apply.
Some PHP functions that start programs or open raw sockets are disabled on shared hosting; see PHP disabled functions on shared hosting. If your application needs its own firewall rules or server modules, use a VPS, which is self-managed with full root access.
For the wider picture, read OWASP Top 10: practical defence for PHP and Node.js. If your site has already been hacked, start with the hacked website checklist.
What is the best way to stop SQL injection in PHP?
Use prepared statements with PDO or mysqli for every query that includes user input, and check table or column names against a fixed allow-list. Input validation helps but does not replace prepared statements.
Is htmlspecialchars enough to prevent XSS?
It is the right tool for values printed inside HTML, when used with ENT_QUOTES and UTF-8. Values placed inside JavaScript or URLs need json_encode or rawurlencode, and user-supplied HTML needs a maintained sanitiser library.
Should I still use the X-XSS-Protection header?
No. Modern browsers removed the XSS filter it controlled. Use a Content Security Policy and proper output escaping instead.
How should I store user passwords in PHP?
Store only a hash made with password_hash using PASSWORD_DEFAULT, and check logins with password_verify. Never store or compare plain-text passwords.
Can I install ModSecurity on shared hosting?
No. On shared hosting the server configuration, including the firewall and web application firewall, is managed for the whole server. On your own VPS you can install and configure ModSecurity yourself.
Does a web application firewall make my code safe?
No. A WAF blocks many common attack patterns, but it is a safety net. Prepared statements, output escaping, CSRF tokens and updates are what actually close the holes in your application.
Ready to harden your application? Start with the OWASP Top 10 guide, compare cPanel hosting and VPS plans, or open a ticket with questions.
Tell us what your application runs and where it is hosted, and we will tell you what the server already does and what is up to your code.
Open a support ticket