Kirby, Apache, and PHP on Windows

I have finally cobbled together a native Windows development environment. This time—due to restrictions on access placed on the work computer, as well as donkiness from the Microsoft Store—I’ve circumvented the Windows Subsystem for Linux route I used to use.

I also like the command line more than MSIs or installer bundles. Deleting directories is easier to me than digging around in Control Panel’s uninstallers and keeping your fingers crossed.

Install the package manager

The first step is grabbing Scoop, a Windows package manager. It doesn’t have the reach of Homebrew, but their operations are very similar. Scoop requires at least version 5 of PowerShell, and you don’t have to run as administrator.

Where Scoop falls down in comparison to Brew (again, besides the range of packages available) is that it relies on Git for maintaining manifests, but doesn’t install Git itself. So, you need Git to update Scoop, but Scoop doesn’t install Git. Type this instead:

scoop install git
scoop bucket add extras
scoop update

Git will install 7Zip as a dependency. Scoop uses “bucket” as nomenclature for bundles of packages. Then the update process will grab the most recent manifests for the main bucket as well as the extras bucket we just added.

Install PHP

I’m only interested in PHP 8.0, so I don’t need the versions bucket that would let me install other releases. scoop install php will do the magic here.

Again—and I’m going to keep pointing out where Homebrew does things, because I primarily use macOS and Homebrew is fantastic—Scoop uses symlinks for configuration directories. As long as you place your configuration files in the appropriate place, they won’t be overwritten by version changes.

For PHP, you’ll want to make edits only in ~/scoop/apps/php/current/cli. You can either edit php.ini directly or add custom .ini files into conf.d.

In the extensions section of php.ini, you need to explicitly set


The php.ini file will accept Windows-style paths.

Activate the curl, gd, fileinfo, and mbstring extensions. If you’re going to use a database, activate the appropriate extension. Since I’m using Kirby, a flat-file CMS, I haven’t bothered.

php —m at the command line will show you what modules you have installed for CLI PHP.

Save the file, and restart PowerShell. Since we need Composer for Kirby installation and maintenance, run scoop install composer. To confirm your PHP’s set up correctly, run

composer create-project getkirby/plainkit

in whatever your project directory is. If you need another extension enabled, Composer will error out and let you know in the terminal. Just uncomment the correct extension and try again.


This is the configuration that took some doing. There were a lot of HTML 500 error codes until I managed to zero in on the Apache configuration responsible for pulling configuration in from the custom php.ini files we set up earlier.

First, scoop install apache. If you’re going to run Apache as a service—which is useful, if you don’t want to keep a terminal window open constantly—then scoop install sudo as well. The sudo utility acts like the Unix version, elevating privileges for a single command.

sudo httpd -k install -n apache

creates a Windows service named apache, and then you can you can adjust the service with

sudo net start|stop apache

Scoop symlinks Apache configuration into


Open httpd.conf and adjust ServerName to localhost and DocumentRoot to whatever folder you’re using. Apache won’t accept Windows-style paths, so make sure to use forward slashes and Unix-style pathing.

In the LoadModules section, activate mod_filter, mod_rewrite, and mod_vhost. At the bottom of the directives, add the following:

LoadModule php_module c:/Users/[user]/scoop/apps/php/current/php8apache2_4.dll

Note that in PHP 8, there’s no version number in php_module.

You need to add SetHandler and ApplicationType directives, but the crucial discovery is to explicitly set the PHPIniDir directive to the current symlink in Scoop.

You’ll want to uncomment the

loadconf httpd/extra/vhosts.conf

directive and add VirtualHost information to extra/vhosts.conf. I use .test domains, so I add the following to c:\Windows\system32\drivers\hosts: domain.tests

Why is that important?

If you don’t set PHPIniDir, no matter how many times you restart Apache or adjust php.ini, the server’s never going to pick up those changes.

Testing adjustments to php.ini is as easy as creating a PHP file in your document root. Add <?php phpinfo(); and load the file in your browser. If you see a blank line for the configuration directory, Apache’s looking in the wrong place for your php.ini.

Caveats and tips

  • Don’t adjust php.ini and also create a custom.ini file. Keep your changes in one place.
  • Once you’ve gotten things working, add the ~/scoop/persist directory to version control.
  • Don’t activate extensions you don’t need, either in Apache or PHP.
  • httpd -t will test your configuration files at the command line. It will also stop parsing at the first error, so expect to use it a lot.
  • If the test flag still can’t help you—like the HTTP 500 codes I got despite Apache CLI telling me syntax was fine—check the logs.
  • Check, double-check, and triple-check your paths. PHP can use Windows-style, Apache requires Unix-style, and add trailing slashes for directories.

Read next
Four good things

Read previous
Deep field

Strangers are friends I haven’t met yet. Drop me a line.