Illustration showing the PHP logo

Need to quickly start a web server to test a PHP application? The PHP interpreter has one built-in! You can use this to rapidly inspect your work without running Apache, NGINX, or a containerization solution.

PHP’s integrated server gets relatively little attention but is quite powerful for development purposes. In this guide, we’ll show how you can use it as an alternative to other micro-servers like Python’s SimpleHTTPServer or the http-server npm package, neither of which can execute PHP scripts.

Using the Built-In Server

The built-in server is a convenience mechanism to help you test PHP sites in environments that lack a fully-fledged HTTP server. It’s available in PHP 5.4 and all later versions. You can run it straight from your working directory without having to set up a virtual host first.

Before using the server, be warned that it’s designed for development use only. The PHP documentation explicitly warns against deploying this server in front of production applications. It’s not sufficiently secure to be exposed on publicly accessible networks.

Starting the Server

The server is started by passing the -S flag to the php executable:

$ php -S localhost:8080
[Fri Jun 10 16:00:00 2022] PHP 8.1.5 Development Server (http://localhost:8080) started

The argument given to the command specifies the server’s listening address. We’ve used port 8080 on localhost in the example above. Now you can visit http://localhost:8080 in your web browser to access the content in your working directory. Any PHP scripts will be executed automatically when you request them.

You can serve a path that’s outside your working directory by setting the -t flag when you start the server:

$ php -S localhost:8080 -t /home/$USER/public_docs

The document root will now be /public_docs within your home folder.

Keep your terminal window open while you’re using the web server. Press Ctrl+C to kill the process once you’re done testing your site. PHP will log each incoming request into your terminal window, including the URI and HTTP method. Any uncaught PHP errors will show up in the logs too.

Enabling Remote Access

Listening on localhost won’t allow incoming connections from other devices on your network. You can permit remote access by binding to instead:

$ php -S

Remember that the server’s not hardened for production use and shouldn’t be publicly exposed. Only allow remote access when it’s absolutely necessary, such as when testing a particular feature on a mobile device. Make sure the port you use isn’t open to the internet.

Request Fallback Matching

PHP will look for index.php and index.html files in the active document root when the incoming request lacks a URI component. If neither of these files exists, the server will keep moving up the directory tree, looking for an index in one of your document root’s parents. This means you can unintentionally end up serving content that lies outside the directory you’ve specified. A 404 Not Found status will be issued when the top of the tree is reached without an index file being found.

Requests that include a URI (such as /file) must be matched exactly by a static file in the document root. Otherwise a 404 will be returned. PHP automatically sets the Content-Type response header to the MIME type of the served file for most popular file extensions.

Using a Router Script

You can optionally configure the web server to call a script on every request. This lets you use your application’s front controller to perform advanced dynamic routing.

The router functionality is enabled by supplying a PHP filename on the command line when you start the server:

$ php -S localhost:8080 router.php

PHP will now use router.php to handle every incoming request. You can route users to the appropriate point in your application by inspecting the request URI:

if ($_SERVER["REQUEST_URI"] === "/dashboard") {
else if ($_SERVER["REQUEST_URI"] === "/profile") {
else {

The output produced by your router script will become the response that’s sent back to the client. An exception is if the script returns false: in this case, PHP will fallback to returning the static file that matches the original request URI.

if (str_starts_with($_SERVER["REQUEST_URI"], "/api")) {
    // Route to the correct API endpoint
    // ... 
else {
    // Serve other routes statically
    return false;

Detecting the Built-In Server From Your PHP Code

Your PHP code can detect whether it’s being called by the built-in web server by inspecting the active interface name. The php_sapi_name() function provides this value. It’ll be set to cli-server when the script was invoked by the integrated server component.

if (php_sapi_name() === "cli-server") {

Handling Multiple Requests Concurrently

The server defaults to running in a single-process synchronous mode by default. Requests are handled individually and block each other from executing until they complete. This is one of the reasons why the server’s unsuitable for production use.

PHP 7.4 added support for handling multiple requests concurrently. It relies on fork() availability and doesn’t work on Windows. The server will fork a new worker to serve each incoming request when this mode is enabled. You can activate it by setting the PHP_CLI_SERVER_WORKERS environment variable to the number of workers you want:

$ PHP_CLI_SERVER_WORKERS=8 php -S localhost:8080

This functionality is still marked as experimental in PHP 8.1.


PHP has a built-in web server that’s a convenient way to test your applications and quickly expose local filesystem content on your local network. It supports PHP script execution, catch-all routing, and static files with most common MIME types.

Although the server now supports an optional forking mode, it’s not advisable to use it in production. It’s intended as a development aid and lacks the customization and security features you’ll need for your public deployments. Where it excels is as a lightweight and integrated alternative to conventional development platforms like WAMP, XAMPP, and Docker containers.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »