start here
Built-In Web Server
PHP includes a small development web server. It lets you run a local PHP site without installing Nginx, Apache, Caddy, or PHP-FPM.
Use it for learning, demos, small local projects, and quick HTTP checks. Do not use it as a production server.
Starting The Server
The basic command is:
php -S 127.0.0.1:8000
That starts a local server on http://127.0.0.1:8000. Press Ctrl+C in the terminal to stop it.
Most projects should use a public document root:
php -S 127.0.0.1:8000 -t public
The -t public option means the server should serve files from the public directory. That keeps source files, configuration, and vendor code outside the web root.
A Minimal public/index.php
Create public/index.php:
<?php
declare(strict_types=1);
header('Content-Type: text/plain');
echo 'Hello from PHP' . PHP_EOL;
echo 'SAPI: ' . PHP_SAPI . PHP_EOL;
// Browser output:
// Hello from PHP
// SAPI: cli-server
Start the server:
php -S 127.0.0.1:8000 -t public
Then open:
http://127.0.0.1:8000/
PHP_SAPI reports cli-server because the built-in server runs from the CLI runtime.
Checking Request Information
The built-in server gives you normal web request values in $_SERVER.
<?php
declare(strict_types=1);
header('Content-Type: application/json');
echo json_encode([
'method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
'query' => $_SERVER['QUERY_STRING'] ?? '',
'sapi' => PHP_SAPI,
], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
echo PHP_EOL;
// Example URL:
// http://127.0.0.1:8000/?page=2
//
// Prints:
// {
// "method": "GET",
// "uri": "/?page=2",
// "query": "page=2",
// "sapi": "cli-server"
// }
This is useful when learning how HTTP requests reach PHP.
Static Files
The built-in server serves static files directly when they exist under the document root. For example:
public/style.css
public/images/logo.png
Those files can be requested as:
http://127.0.0.1:8000/style.css
http://127.0.0.1:8000/images/logo.png
If a file does not exist, the server may route the request to a PHP script depending on how you started it.
Router Scripts
A router script gives you more control. It can let existing static files pass through and send everything else to index.php.
Create router.php in the project root:
<?php
declare(strict_types=1);
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$file = __DIR__ . '/public' . $path;
if (is_string($path) && $path !== '/' && is_file($file)) {
return false;
}
require __DIR__ . '/public/index.php';
Start the server with the router:
php -S 127.0.0.1:8000 router.php
Returning false tells the built-in server to serve the requested static file itself. Otherwise the router includes the front controller.
Binding Address
Use 127.0.0.1 for local-only development:
php -S 127.0.0.1:8000 -t public
Binding to 0.0.0.0 listens on all network interfaces:
php -S 0.0.0.0:8000 -t public
Only use 0.0.0.0 when you deliberately want another device or container to reach the server. Do not expose the built-in server to the public internet.
Limitations
The built-in server is not a production replacement for Nginx, Apache, Caddy, or a managed runtime.
Important limitations:
- it is designed for development
- it does not replace proper TLS, static file, caching, compression, or process management setup
- it runs as the current user
- it uses the CLI PHP configuration
- it does not prove PHP-FPM is configured correctly
If production uses PHP-FPM behind Nginx, a local built-in server can help you develop routes, but it cannot verify the production web server configuration.
What You Should Be Able To Do
After this lesson, you should be able to start the built-in server, choose a document root, inspect request information, use a router script, and explain why the built-in server is for development only.
For junior PHP work, this matters because the built-in server is the fastest way to see PHP through HTTP while learning. The professional habit is knowing where its usefulness ends.
Practice
Practice: Start A Local PHP Server
Create a tiny public/index.php file and run it with PHP's built-in server.
Task
Build an index.php that:
- sets
Content-Type: text/plain - prints a greeting
- prints
PHP_SAPI - prints the request URI
Start the server with -t public and visit it in the browser.
Use strict types. Keep the expected browser output inside the PHP code block as comments.
Afterward, add a short note explaining why public should be the document root.
Show solution
This public/index.php proves the request is going through the built-in server.
<?php
declare(strict_types=1);
header('Content-Type: text/plain');
echo 'Hello from the built-in server' . PHP_EOL;
echo 'SAPI: ' . PHP_SAPI . PHP_EOL;
echo 'URI: ' . ($_SERVER['REQUEST_URI'] ?? '/') . PHP_EOL;
// Browser output for http://127.0.0.1:8000/hello?page=1:
// Hello from the built-in server
// SAPI: cli-server
// URI: /hello?page=1
Start it with:
php -S 127.0.0.1:8000 -t public
public should be the document root so configuration, source files, private storage, and dependencies are not directly web-accessible.
Task: Check Request Info
Create a small JSON endpoint that reports request information from the built-in server.
Requirements
Build an endpoint that prints:
- request method
- request URI
- query string
PHP_SAPI
Use strict types and json_encode() with JSON_THROW_ON_ERROR. Keep example output inside the PHP code block as comments.
Afterward, add a short note explaining why request data belongs in $_SERVER for this example, not $argv.
Show solution
This endpoint returns a small JSON summary of the incoming HTTP request.
<?php
declare(strict_types=1);
header('Content-Type: application/json');
echo json_encode([
'method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
'query' => $_SERVER['QUERY_STRING'] ?? '',
'sapi' => PHP_SAPI,
], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
echo PHP_EOL;
// Example URL:
// http://127.0.0.1:8000/?page=2
//
// Prints:
// {
// "method": "GET",
// "uri": "/?page=2",
// "query": "page=2",
// "sapi": "cli-server"
// }
Request data belongs in $_SERVER here because the code is handling an HTTP request. $argv is for command-line arguments in CLI scripts.
Task: Router Script
Create a router script for the built-in server.
Requirements
Build a router.php that:
- checks the requested path
- lets existing files under
publicbe served by the built-in server - sends all other requests to
public/index.php
Use strict types. Include the command used to start the server.
Afterward, add a short note explaining what return false means in a built-in server router script.
Show solution
This router serves real static files and sends application routes to the front controller.
<?php
declare(strict_types=1);
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$file = __DIR__ . '/public' . $path;
if (is_string($path) && $path !== '/' && is_file($file)) {
return false;
}
require __DIR__ . '/public/index.php';
Start the server with:
php -S 127.0.0.1:8000 router.php
In a built-in server router script, return false means "let the server handle this request normally". That is useful for static files such as CSS, JavaScript, and images.