$_SERVER

Ma Mahalakshmi V Updated 14 Nov 2026
8 min read ·Lesson 96 of 256

What Is $_SERVER?

In simple terms: $_SERVER is an array with information about the current request and the environment the script runs in: which URL was requested, which HTTP method was used, the visitor's IP address, request headers, the script's file path, and more.

Technically: $_SERVER is a superglobal array populated by PHP from the web server (through the server API, such as PHP-FPM or Apache's module) and from the process environment. Its exact contents depend on the web server and its configuration; PHP does not guarantee that every key exists.

Why It Is Needed

Many decisions depend on details of the request itself rather than on submitted data:

  • Was the form submitted (POST) or just displayed (GET)?
  • Which path was requested (for routing)?
  • Is the connection secure (HTTPS)?
  • What language does the browser prefer?
  • From which IP address did the request come (for rate limiting or logging)?

Common Keys

Request Information

KeyExample ValueMeaning
REQUEST_METHODGET, POST, PUT, DELETEHTTP method
REQUEST_URI/products.php?category=books&page=2Path and query string as requested
QUERY_STRINGcategory=books&page=2Only the query string
REQUEST_TIME1791541200Unix timestamp of the request start
REQUEST_TIME_FLOAT1791541200.2531Same, with microseconds
SERVER_PROTOCOLHTTP/1.1Protocol version
HTTPSonNon-empty when the request used HTTPS (may be missing or off otherwise)
CONTENT_TYPEapplication/jsonType of the request body
CONTENT_LENGTH57Size of the request body in bytes

Script and Server Information

KeyExample ValueMeaning
SCRIPT_NAME/products.phpURL path of the running script
SCRIPT_FILENAME/var/www/shop/public/products.phpAbsolute file path of the script
DOCUMENT_ROOT/var/www/shop/publicWeb server document root
PHP_SELF/products.phpScript path plus any extra path info (see Security)
SERVER_NAMEgocourse.inServer name from the web server configuration
SERVER_PORT443Port the server received the request on
SERVER_SOFTWAREnginx/1.26.0Web server name and version

Client and Header Information

KeyExample ValueMeaning
REMOTE_ADDR203.0.113.7IP address of the client (or of the last proxy)
HTTP_HOSTgocourse.inHost header sent by the client
HTTP_USER_AGENTMozilla/5.0 (...)Browser identification
HTTP_ACCEPT_LANGUAGEta-IN,ta;q=0.9,en;q=0.8Preferred languages
HTTP_REFERERhttps://google.com/Page that linked here (often absent)

How Request Headers Become Keys

Every request header is available with the prefix HTTP_, in uppercase, with hyphens replaced by underscores:

Header$_SERVER Key
Accept-LanguageHTTP_ACCEPT_LANGUAGE
X-Requested-WithHTTP_X_REQUESTED_WITH
AuthorizationHTTP_AUTHORIZATION (some servers need extra configuration to pass it)

Content-Type and Content-Length are exceptions: they appear as CONTENT_TYPE and CONTENT_LENGTH, without the prefix.


Reading $_SERVER Safely

Keys may be missing depending on the server, the request, and whether the script runs in the command line. Always provide defaults:

<?php
// Example request: GET https://gocourse.in/products.php?category=books&page=2

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$path = parse_url($uri, PHP_URL_PATH);
$query = $_SERVER['QUERY_STRING'] ?? '';
$isHttps = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
$referrer = $_SERVER['HTTP_REFERER'] ?? 'none';

echo "Method: $method\n";
echo "Path: $path\n";
echo "Query: $query\n";
echo "HTTPS: " . ($isHttps ? 'yes' : 'no') . "\n";
echo "Referrer: $referrer\n";

Output (for the example request, visited directly)

Method: GET
Path: /products.php
Query: category=books&page=2
HTTPS: yes
Referrer: none
  • parse_url(..., PHP_URL_PATH) extracts the path from REQUEST_URI, dropping the query string.
  • The HTTPS check accepts any non-empty value except off, which some servers (such as IIS) use for plain HTTP.
  • HTTP_REFERER is missing when the visitor typed the address, used a bookmark, or the browser's privacy settings removed it.

Detecting the Request Method

The most common use of $_SERVER is distinguishing "show the form" from "process the form":

<?php
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
    // validate and process $_POST, then redirect
} else {
    // display the empty form
}

Checking the method is more reliable than checking whether $_POST is empty: a POST request can have an empty body, and a request whose body exceeds post_max_size arrives with an empty $_POST even though it was submitted. See $_POST.


Practical Example: A Small Router

A router maps the method and path of each request to the code that handles it. Keeping the logic in a function makes it testable without a web server:

<?php
function route(string $method, string $uri): array
{
    $path = rtrim(parse_url($uri, PHP_URL_PATH) ?? '/', '/') ?: '/';

    $routes = [
        'GET /' => 'Home page',
        'GET /products' => 'Product list',
        'POST /products' => 'Product created',
    ];

    if (isset($routes["$method $path"])) {
        return [200, $routes["$method $path"]];
    }

    $pathExists = array_filter(
        array_keys($routes),
        fn(string $route): bool => explode(' ', $route, 2)[1] === $path
    );

    return $pathExists ? [405, 'Method not allowed'] : [404, 'Page not found'];
}

$requests = [
    ['GET', '/'],
    ['GET', '/products?page=2'],
    ['POST', '/products/'],
    ['DELETE', '/products'],
    ['GET', '/about'],
];

foreach ($requests as [$method, $uri]) {
    [$status, $body] = route($method, $uri);
    printf("%-6s %-18s → %d %s\n", $method, $uri, $status, $body);
}

// In a real front controller:
// [$status, $body] = route($_SERVER['REQUEST_METHOD'] ?? 'GET', $_SERVER['REQUEST_URI'] ?? '/');
// http_response_code($status);
// echo $body;

Output

GET    /                  → 200 Home page
GET    /products?page=2   → 200 Product list
POST   /products/         → 200 Product created
DELETE /products          → 405 Method not allowed
GET    /about             → 404 Page not found

Step-by-Step Explanation

  1. parse_url() removes the query string, and rtrim(..., '/') removes a trailing slash, so /products/ and /products match the same route. The root path / would become an empty string, so ?: '/' restores it.
  2. Routes are stored as "METHOD /path" keys for fast lookup.
  3. If the path exists but not for this method, the correct HTTP status is 405 Method Not Allowed; if the path does not exist at all, it is 404 Not Found.
  4. The commented lines show how a front controller (public/index.php) feeds $_SERVER values into the function and sends the response.

Frameworks use the same principle with far more features: route parameters (/products/{id}), middleware, and controllers.


Security Considerations

Many $_SERVER values come directly from the client and can be manipulated.

PHP_SELF and Cross-Site Scripting

<!-- Vulnerable -->
<form action="<?= $_SERVER['PHP_SELF'] ?>" method="post">

PHP_SELF includes any extra path information after the script name. A request to /contact.php/"><script>alert(1)</script> can make PHP_SELF contain HTML, which is then printed into the page. Safer options:

<form action="" method="post">                                        <!-- submit to the current URL -->
<form action="<?= htmlspecialchars($_SERVER['SCRIPT_NAME']) ?>" method="post">

HTTP_HOST Can Be Spoofed

The Host header is chosen by the client. Using it to build absolute URLs — for example, in password reset emails — allows host header injection: an attacker requests a reset for a victim with a forged Host, and the email contains a link to the attacker's domain.

Fix: Build absolute URLs from a configured base URL (for example, APP_URL=https://gocourse.in in your configuration), not from HTTP_HOST. Also configure the web server to accept only your real domain names.

REMOTE_ADDR and Proxies

REMOTE_ADDR is the IP address of whoever connected to the server. Behind a load balancer, CDN, or reverse proxy, that is the proxy's address, and the real client address is passed in a header such as X-Forwarded-For. That header can be forged by anyone, so it may only be trusted when the request actually came from your own proxy:

<?php
function clientIp(array $server, array $trustedProxies): string
{
    $remote = $server['REMOTE_ADDR'] ?? '0.0.0.0';

    if (!in_array($remote, $trustedProxies, true) || empty($server['HTTP_X_FORWARDED_FOR'])) {
        return $remote;
    }

    $chain = array_map('trim', explode(',', $server['HTTP_X_FORWARDED_FOR']));

    foreach (array_reverse($chain) as $ip) {
        if (!in_array($ip, $trustedProxies, true) && filter_var($ip, FILTER_VALIDATE_IP)) {
            return $ip;
        }
    }

    return $remote;
}

$trusted = ['10.0.0.2'];

echo clientIp(['REMOTE_ADDR' => '203.0.113.7'], $trusted) . "\n";
echo clientIp(['REMOTE_ADDR' => '10.0.0.2', 'HTTP_X_FORWARDED_FOR' => '198.51.100.23'], $trusted) . "\n";
echo clientIp(['REMOTE_ADDR' => '203.0.113.7', 'HTTP_X_FORWARDED_FOR' => '1.2.3.4'], $trusted) . "\n";
echo clientIp(['REMOTE_ADDR' => '10.0.0.2', 'HTTP_X_FORWARDED_FOR' => '1.2.3.4, 198.51.100.23'], $trusted) . "\n";

Output

203.0.113.7
198.51.100.23
203.0.113.7
198.51.100.23

How It Works

  1. A direct connection (not from a trusted proxy) returns REMOTE_ADDR; any X-Forwarded-For header sent by the client is ignored (third case).
  2. When the request comes from the trusted proxy, the function reads the forwarded chain from right to left. Each proxy appends the address it received the request from, so the rightmost address not belonging to your proxies is the real client.
  3. In the fourth case, the client tried to fake its address by sending 1.2.3.4 itself; the proxy appended the real address 198.51.100.23, which is the one returned.

The function receives the server array as a parameter (clientIp($_SERVER, $trusted) in real code), which makes it easy to test with different inputs.


$_SERVER in the Command Line

When running a script with php script.php, there are no HTTP keys (REQUEST_METHOD, HTTP_HOST, and so on are missing). Instead, $_SERVER['argv'] and $_SERVER['argc'] contain the command-line arguments, and the environment variables of the shell are included. Code that may run in both contexts should use ?? defaults for every HTTP-related key (see PHP Command Line (CLI)).


Common Mistakes

Assuming a Key Always Exists

Effect: Warnings, especially for HTTP_REFERER, HTTPS, and in CLI scripts.

Fix: Use ?? with sensible defaults.

Printing PHP_SELF Unescaped

Effect: Cross-site scripting.

Fix: Use action="", or escape SCRIPT_NAME with htmlspecialchars().

Effect: Host header injection in emails and redirects.

Fix: Use a configured application URL.

Trusting X-Forwarded-For Unconditionally

Effect: Attackers bypass IP-based rate limits or appear to come from any address.

Fix: Trust forwarded headers only when REMOTE_ADDR is your own proxy.

Using HTTP_REFERER for Security

Effect: The header is optional and easily forged.

Fix: Use CSRF tokens to verify form submissions (see CSRF Protection).


Best Practices

  • Read $_SERVER values with defaults and validate them.
  • Use REQUEST_METHOD to decide between displaying and processing forms.
  • Parse REQUEST_URI with parse_url() for routing.
  • Treat all client-controlled values (HTTP_*, PHP_SELF, REQUEST_URI) as untrusted input.
  • Wrap request-dependent logic in functions that accept values as parameters, so it can be tested without a web server.

Official reference: https://www.php.net/manual/en/reserved.variables.server.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Tutorial Index PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions Modify Strings Concatenate Strings Slicing Strings Escape Characters String Interpolation Heredoc and Nowdoc Search in Strings Multibyte Strings PHP Floats PHP Casting PHP Type Juggling PHP Math PHP Random Numbers PHP Constants PHP Magic Constants PHP Operators Arithmetic Operators Assignment Operators Comparison Operators Increment / Decrement Operators Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence PHP If...Else...Elseif PHP If PHP If Operators PHP If...Else PHP Shorthand if PHP Nested if PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement Continue Statement PHP Functions User-defined Functions Function Arguments Default Argument Values Pass by Reference Named Arguments Variadic Functions Return Values Type Declarations in Functions Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS $_SERVER $_REQUEST $_POST $_GET $_FILES $_COOKIE $_SESSION \$\_ENV PHP RegEx RegEx Patterns RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions PHP Form Handling PHP Form Validation PHP Form Required PHP Form URL/E-mail Form Input Sanitizing Escaping Output (htmlspecialchars) Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting DateTime Class Timezones PHP Include Include vs Require PHP File Handling PHP File Open/Read PHP File Create/Write File Delete / Rename / Copy PHP Directories PHP CSV Files PHP File Upload PHP Cookies PHP Sessions PHP Filters PHP Filters Advanced PHP Callback Functions PHP JSON PHP Exceptions Error Handling Custom Error Handler Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators PHP What is OOP PHP Classes/Objects The $this Keyword PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Destructor PHP Access Modifiers Readonly Properties PHP Inheritance Method Overriding The final Keyword PHP Class Constants PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning Comparing Objects Anonymous Classes PHP Enums PHP Namespaces Autoloading Classes PHP Iterables Iterators Object Serialization Strict Types Union Types Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention CSRF Protection Session Security Secure Cookies File Upload Security Filesystem Security Error Reporting in Production Hiding PHP Encryption with Sodium PHP Configuration (php.ini) PHP Command Line (CLI) Composer PSR-4 Autoloading Installing Extensions (PECL / PIE) Debugging PHP OPcache & Performance Garbage Collection PHP-FPM Migrating to PHP 8 Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Image Upload Gallery Project: Shopping Cart with Sessions Project: Simple Blog Project: REST API with PHP
Ask about this post
AI Ask about this post

Ask questions about $_SERVER and get answers drawn from it.

Signed-in readers only.