Hiding PHP
Hiding PHP What Does "Hiding PHP" Mean? In simple terms: By default, a PHP website announces itself in several ways: response headers say X-Powered-By: PHP/8.4.3 , URLs end in .php , the session cooki
Hiding PHP
What Does "Hiding PHP" Mean?
In simple terms: By default, a PHP website announces itself in several ways: response headers say X-Powered-By: PHP/8.4.3, URLs end in .php, the session cookie is called PHPSESSID, and error pages show PHP file paths. Hiding PHP removes these clues, so visitors — and automated attack tools — learn less about your technology stack and exact versions.
Technically: "Hiding PHP" is a set of configuration and design choices that reduce information disclosure: disabling version headers (expose_php, server tokens), using a front controller with clean URLs, renaming default cookies, disabling error display, and removing default or diagnostic files. It is security through obscurity — useful as an extra layer that slows down automated scanners, but never a replacement for real security measures and keeping software updated.
Removing Version Headers
The X-Powered-By Header
With default settings, every PHP response includes a header like this:
HTTP/1.1 200 OK
Server: nginx/1.24.0
X-Powered-By: PHP/8.4.3
Content-Type: text/html; charset=UTF-8
Attackers and bots scan for specific versions with known vulnerabilities. Disable the header in php.ini:
expose_php = Off
expose_php can only be set in php.ini (it is a system-level setting). If you cannot change php.ini, remove the header in your application before any output:
<?php
header_remove('X-Powered-By');
Web Server Version Information
The web server reveals its own version too. Hide it in the server configuration:
# Nginx (http block)
server_tokens off;
# Apache
ServerTokens Prod
ServerSignature Off
After the Changes
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
The response no longer states which PHP or server version is running.
Clean URLs With a Front Controller
URLs like /products.php?id=7 reveal the language immediately. Modern PHP applications route all requests through a single entry point — the front controller (public/index.php) — and use clean URLs such as /products/7.
Web Server Configuration
# Nginx: send everything that is not an existing file to index.php
location / {
try_files $uri /index.php$is_args$args;
}
location = /index.php {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
}
# Do not execute any other .php file
location ~ \.php$ {
return 404;
}
# Apache (.htaccess in public/)
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
A Minimal Router
<?php
// public/index.php
function route(string $requestUri): string
{
$path = parse_url($requestUri, PHP_URL_PATH) ?: '/';
$path = $path === '/' ? '/' : rtrim($path, '/');
$routes = [
'/' => 'home',
'/courses' => 'courses',
'/contact' => 'contact',
];
return $routes[$path] ?? 'not-found';
}
foreach (['/', '/courses', '/courses/', '/contact?ref=footer', '/admin.php'] as $uri) {
echo str_pad($uri, 20) . '→ ' . route($uri) . "\n";
}
// In the real front controller:
// $page = route($_SERVER['REQUEST_URI'] ?? '/');
// if ($page === 'not-found') { http_response_code(404); }
// require __DIR__ . "/../src/pages/$page.php";
Output
/ → home
/courses → courses
/courses/ → courses
/contact?ref=footer → contact
/admin.php → not-found
How It Works
- The web server forwards every request that is not a real static file (CSS, images) to
index.php. - The router looks at the path only (the query string is ignored for routing) and normalizes a trailing slash.
- Each path maps to a page name from a fixed list; unknown paths — including attempts to call
.phpfiles — result in "not found". - Page files are loaded from
src/pages/, which lies outside the document root and cannot be requested directly.
Besides hiding the technology, a front controller gives a single place for bootstrapping, security headers, CSRF checks, and error handling. Frameworks such as Laravel and Symfony are built this way.
Other Telltale Signs
| Clue | How to Hide It |
|---|---|
Session cookie named PHPSESSID | Set session.name to a neutral name such as __Host-SID (see Session Security) |
| PHP error messages with file paths | display_errors = Off (see Error Reporting in Production) |
Default and diagnostic files (phpinfo.php, test.php, info.php) | Never deploy them |
composer.json, composer.lock, .env, vendor/ reachable by URL | Keep only public/ in the document root |
Framework-specific paths and cookies (laravel_session, /wp-admin) | Rename where the framework allows it; keep the software updated regardless |
| Default server error pages | Replace with custom, neutral error pages |
Some guides suggest mapping other extensions (.html, .asp) to PHP to confuse attackers. This adds complexity and can cause security problems (for example, uploaded .html files being executed); clean URLs achieve the same goal properly.
Keeping Expectations Realistic
Hiding PHP does not make a vulnerable application safe:
- Automated attacks often simply try exploits for many technologies, without checking versions first.
- Response timing, HTML structure, cookies, and framework behavior can still reveal the stack.
- The real protection is current software, secure code, and secure configuration.
Think of hiding PHP as removing labels from a locked door: it costs little and reduces noise from scanners, but the lock is what matters.
Common Mistakes
Relying on Obscurity Instead of Updates
Effect: Old, vulnerable versions remain exploitable regardless of hidden headers.
Fix: Keep PHP, extensions, the web server, and dependencies updated.
Removing Headers but Leaving Error Display On
Effect: Error pages still reveal paths and versions.
Fix: Disable display_errors in production.
Executing Arbitrary .php Files in the Document Root
Effect: Forgotten scripts (old tools, test files) remain reachable.
Fix: Allow PHP execution only for the front controller.
Leaving phpinfo() Files on Servers
Effect: Complete configuration and environment disclosure.
Fix: Delete them; use protected admin tools or the CLI instead.
Best Practices
- Set
expose_php = Offand hide web server version tokens. - Route all requests through a front controller with clean URLs.
- Execute only the front controller; keep application code outside the document root.
- Rename default cookies, disable error display, and remove diagnostic files.
- Treat hiding as an extra layer — keep everything updated and securely coded.
Official reference: https://www.php.net/manual/en/security.hiding.php