Skip to content

PHP Headers — HTTP Header Manipulation, Redirects, and Content Types

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about PHP Headers. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP headers are HTTP header lines sent from the server to the browser before any body content, enabling redirects, content type declarations, cache control, and file downloads through the header function.

Why It Matters

HTTP headers control how the browser interprets your response. A redirect header tells the browser to go to another URL. A content-type header tells the browser whether to render HTML, display an image, or download a file. Cache headers control whether pages are cached. Understanding headers is essential for building REST APIs, file download handlers, and any application that needs precise HTTP control.

Real-World Use

A login system redirects to the dashboard with header('Location: dashboard.php'). A PDF generation script sets Content-Type: application/pdf. An API returns JSON with Content-Type: application/json. A download handler forces file download with Content-Disposition: attachment. DodaTech uses headers for authentication redirects, security headers, and API responses.

What You Will Learn

  • Setting HTTP headers with the header() function
  • Redirect responses with Location headers
  • Content-Type headers for different response formats
  • Cache control and expiration headers
  • File download headers with Content-Disposition
  • Status codes and custom headers
  • Security headers: CSP, HSTS, X-Frame-Options

Learning Path

flowchart LR
  A[Cookies] --> B[Headers
You are here] B --> C[File Upload] C --> D[JSON API] D --> E[Error Handling] style B fill:#f90,color:#fff

Basic Header Function

The header() function sets a raw HTTP header:

<?php
header('Content-Type: text/html; charset=utf-8');
header('X-Custom-Header: MyValue');

Rules for Using header()

  1. Call header() before any output (HTML, echo, whitespace)
  2. Use headers_sent() to check if headers were already sent
  3. Call header_remove() to remove a previously set header
  4. Use headers_list() to see all headers that will be sent

Redirects

The most common use of headers is redirecting the browser:

<?php
// Simple redirect
header('Location: /dashboard.php');
exit;  // Always call exit after redirect

// With status code
header('Location: /new-page.php', true, 301);  // Permanent redirect
header('Location: /temporary.php', true, 302); // Temporary redirect

// Redirect with delay (meta refresh alternative)
header('Refresh: 5; URL=/new-page.php');
echo 'You will be redirected in 5 seconds.';

Always call exit or die after a redirect header. Otherwise the script continues executing, and the browser may ignore the redirect if more output follows.

Content-Type Headers

Tell the browser what kind of content you are sending:

<?php
// HTML
header('Content-Type: text/html; charset=utf-8');

// Plain text
header('Content-Type: text/plain; charset=utf-8');

// JSON API response
header('Content-Type: application/json');
echo json_encode(['status' => 'ok', 'data' => $data]);

// XML
header('Content-Type: application/xml');
echo $xmlString;

// CSS
header('Content-Type: text/css');

// JavaScript
header('Content-Type: application/javascript');

// Image
header('Content-Type: image/png');
readfile('image.png');

File Download Headers

Force a file to download instead of displaying in the browser:

<?php
$file = 'document.pdf';
$filename = 'user-document.pdf';

// Validate file exists
if (!file_exists($file)) {
    header('HTTP/1.1 404 Not Found');
    echo 'File not found';
    exit;
}

// Set download headers
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));

// Clear output buffer and send file
ob_clean();
flush();
readfile($file);
exit;

Serving Different File Types for Download

<?php
function serveFile(string $filePath, string $downloadName = ''): void {
    if (!file_exists($filePath)) {
        header('HTTP/1.1 404 Not Found');
        exit;
    }

    $mimeTypes = [
        'pdf' => 'application/pdf',
        'zip' => 'application/zip',
        'jpg' => 'image/jpeg',
        'png' => 'image/png',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ];

    $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $mime = $mimeTypes[$ext] ?? 'application/octet-stream';
    $name = $downloadName ?: basename($filePath);

    header('Content-Type: ' . $mime);
    header('Content-Disposition: attachment; filename="' . $name . '"');
    header('Content-Length: ' . filesize($filePath));
    readfile($filePath);
}

Status Codes

Set HTTP status codes with the header function:

<?php
header('HTTP/1.1 200 OK');           // Success
header('HTTP/1.1 201 Created');      // Resource created
header('HTTP/1.1 301 Moved Permanently');  // Permanent redirect
header('HTTP/1.1 400 Bad Request');  // Client error
header('HTTP/1.1 401 Unauthorized'); // Authentication required
header('HTTP/1.1 403 Forbidden');    // Permission denied
header('HTTP/1.1 404 Not Found');    // Resource not found
header('HTTP/1.1 500 Internal Server Error'); // Server error

In PHP 5.4+, use http_response_code():

<?php
http_response_code(404);
echo 'Page not found';

Cache Control Headers

Control whether and how browsers and proxies cache your content:

<?php
// No caching
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 3600) . ' GMT');

// Cache for 1 hour
header('Cache-Control: public, max-age=3600');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');

// Cache for 1 week
$expires = time() + 86400 * 7;
header('Cache-Control: public, max-age=' . (86400 * 7));
header('Expires: ' . gmdate('D, d M Y H:i:s', $expires) . ' GMT');

// Validate with ETag
$etag = md5_file(__FILE__);
header('ETag: "' . $etag . '"');
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
    trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $etag) {
    header('HTTP/1.1 304 Not Modified');
    exit;
}

Security Headers

Modern web applications should set security headers to protect against common attacks:

<?php
// Prevent MIME-type sniffing
header('X-Content-Type-Options: nosniff');

// Prevent clickjacking
header('X-Frame-Options: DENY');

// Enable XSS filter in older browsers
header('X-XSS-Protection: 1; mode=block');

// Strict Transport Security (HTTPS only)
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');

// Content Security Policy
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");

// Referrer Policy
header('Referrer-Policy: strict-origin-when-cross-origin');

// Permissions Policy
header('Permissions-Policy: geolocation=(), microphone=(), camera=()');

CORS Headers

Allow cross-origin requests from other domains:

<?php
// Allow all origins (simple APIs)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');

// Specific origin (secure)
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allowed = ['https://app.example.com', 'https://admin.example.com'];
if (in_array($origin, $allowed)) {
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Vary: Origin');
}

// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header('HTTP/1.1 204 No Content');
    exit;
}

Common Mistakes

1. Output Before header() Call

echo "Hello";
header('Location: /login.php');  // Warning: Cannot modify header information

Any whitespace, HTML, or echo before header() causes this error. Use output buffering if you cannot avoid output.

2. Forgetting exit After Redirect

header('Location: /dashboard.php');
// Script continues executing!

Always call exit after a Location header to stop execution.

3. Not Checking headers_sent()

if (!headers_sent()) {
    header('Location: /error.php');
}

Check headers_sent() before sending a header in code that may have already produced output.

4. Wrong Status Code Syntax

header('401 Unauthorized');  // Wrong
header('HTTP/1.1 401 Unauthorized');  // Correct

5. Not Setting CORS Headers for API Calls

If your API is called from a different domain, the browser blocks the request without proper CORS headers. Always set them for public APIs.

Practice Questions

  1. Why must header() be called before any output? HTTP headers must be sent before the response body. Once output starts, headers cannot be modified.

  2. What is the difference between a 301 and 302 redirect? 301 is permanent (browsers cache the redirect). 302 is temporary. Use 301 for moved pages, 302 for temporary redirects.

  3. How do you force a file to download instead of displaying? Set Content-Disposition: attachment; filename="file.pdf" and Content-Type: application/octet-stream.

  4. What security headers should every site set? X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Strict-Transport-Security, Referrer-Policy.

  5. Challenge: Build a script that serves different content types based on a URL parameter: ?format=json, ?format=xml, ?format=download, with appropriate headers for each.

Mini Project: JSON API Router

Create a simple JSON API with proper headers:

<?php
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Cache-Control: no-store');

function jsonResponse(mixed $data, int $status = 200): never {
    http_response_code($status);
    echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

function jsonError(string $message, int $status = 400): never {
    jsonResponse(['error' => true, 'message' => $message], $status);
}

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

match (true) {
    $path === '/api/users' => jsonResponse([
        ['id' => 1, 'name' => 'Alice'],
        ['id' => 2, 'name' => 'Bob'],
    ]),
    preg_match('#^/api/users/(\d+)$#', $path, $m) === 1 => jsonResponse([
        'id' => (int)$m[1], 'name' => 'User ' . $m[1]
    ]),
    default => jsonError('Not found', 404),
};

FAQ

Can I set multiple headers with the same name?

Yes. Call header() multiple times. The second call replaces the first unless you pass false as the third parameter: header('X-Custom: val', false).

What happens if I send a redirect without exit?

PHP continues executing. The browser may still redirect, but subsequent output can corrupt the response. Always call exit after redirect.

How do I check what headers will be sent?

Call headers_list() before any output. It returns an array of all registered headers.

What is output buffering and how does it help?

Output buffering captures all output in a buffer. Headers can be set anytime while buffering. Call ob_start() at the top of your script.

Can I remove a header after setting it?

Yes, use header_remove('Header-Name'). To remove all headers, call header_remove() with no arguments.

What is Next

Now that you understand headers, proceed to File Upload to learn secure file upload handling. Then read JSON API to build RESTful API endpoints.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro