# phpenv: a rbenv-style PHP version manager for per-project builds

> phpenv is a shell-based PHP version manager that switches interpreters per directory through a .php-version file. It is a good fit for developers who build PHP from source and need several releases side by side, and a poor fit for anyone expecting prebuilt binaries.

**phpenv/phpenv** — Simple PHP version management. Ever wondered why you can't run a PHP app on your own development machine?

- Repository: https://github.com/phpenv/phpenv
- Stars: 1,871 · Forks: 143
- Language: Shell
- License: MIT
- Published: 2026-08-08 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/phpenv-phpenv

## What phpenv is for, and who it is not for

The problem phpenv addresses is narrow and concrete: one machine, several PHP releases, and a project that only works on one of them. The README frames it as a question, asking why you cannot run a PHP app on your own development machine, and answers it with per-directory version selection. Instead of editing a global php.ini or juggling PATH entries by hand, you drop a .php-version file into a project directory and phpenv resolves the right interpreter when you cd in.

That design assumes something important: phpenv does not download PHP for you. Each version is expected to be a directory resulting from a PHP installation, with its own php, php-fpm, pecl, pyrus and php.ini. Building those directories is the job of a separate plugin, php-build. So phpenv is a dispatcher, not a package manager. If what you want is a single command that fetches a prebuilt PHP 8.3 binary, this project is the wrong tool, and the README does not pretend otherwise.

## How the shim and .php-version lookup actually works

At install time phpenv injects itself into your PATH. From then on, invoking php, php-fpm or another PHP-related executable hits a phpenv shim first rather than a real binary. The shim scans the current directory for a file named .php-version. If that file exists, its contents determine which version applies to that directory. phpenv then looks up the version among the directories under ~/.phpenv/versions/ and calls the matching file inside it.

Two consequences follow. The first is that resolution is directory-based, so two projects in different folders can run different PHP releases without any global state changing. The second is that the versions directory is the source of truth. A version that is not present as a directory simply cannot be selected, which is why the README tells you to run phpenv rehash whenever you install a new PHP binary. Shims are cached, and a stale cache is the most likely reason a freshly built interpreter appears to be missing.

The README also states that almost every aspect of the mechanism is customizable through plugins written in bash. That is the extension surface: if the built-in lookup does not match your workflow, you are expected to write shell, not configure a settings file.

## Installing phpenv with Homebrew or a git checkout

On macOS and Linux, the shortest path is the Homebrew tap. The README gives this pair of commands, the first to install and the second to wire phpenv into your shell:

```bash
brew install phpenv/tap/phpenv
echo 'eval "$(phpenv init -)"' >> ~/.profile
exec $SHELL -l
```

After the shell restarts, phpenv should respond on the command line. Note that the README adds a caveat here: to build PHP versions you also need the php-build plugin, which Homebrew does not install for you.

The manual route clones phpenv into ~/.phpenv, which is also where versions and plugins live:

```bash
git clone https://github.com/phpenv/phpenv.git ~/.phpenv
echo 'export PATH="$HOME/.phpenv/bin:$PATH"' >> ~/.profile
echo 'eval "$(phpenv init -)"' >> ~/.profile
exec $SHELL -l
```

The README warns that ~/.profile is only an example. If you use bash, check whether ~/.bash_profile exists; other shells have their own initialization files. Appending to a file your shell never reads is a silent failure, and it is the first thing to check if phpenv init appears to do nothing.

A first real use is choosing a version for one project. The README's example changes into a project directory and pins PHP 8.3.13:

```sh
cd Projects/my-php-project
# choose PHP 8.3.13
phpenv local 8.3.13
```

That writes 8.3.13 into .php-version in the current directory, so the choice travels with the project. To build that interpreter in the first place, install php-build as a plugin and then run phpenv install with the version you want:

```bash
git clone https://github.com/php-build/php-build $(phpenv root)/plugins/php-build
phpenv install <php-version>
phpenv rehash
```

Expect the install step to compile PHP, which takes time and needs build dependencies on the host. The README does not document rollback or an uninstall procedure for a version once it is built.

## Build configuration lives in php-build, not phpenv

Because phpenv delegates compilation, the knobs you care about during a build belong to php-build. The README states that php-build compiles PHP with a default set of options, per-version configure options taken from the PHP build definition, and configure options supplied through environment variables. If you need to change how PHP is built on your system, the documented variables are PHP_BUILD_CONFIGURE_OPTS for configure flags and PHP_BUILD_INSTALL_EXTENSION for extensions.

This split is worth understanding before you file a bug. A missing extension, a wrong library path or an unexpected configure default is a php-build concern, and the fix is an environment variable or a build definition, not a phpenv setting. phpenv only decides which already-built directory a command resolves to. The README also notes that pecl extensions can be built into PHP during the build or added manually afterwards, which gives you a second route when a build definition does not cover what you need.

## Serving PHP: php-fpm, Apache, and the permission detail

The README's preferred way to connect a phpenv-built PHP to a web server is php-fpm. Under this arrangement PHP runs with the permissions of the invoking user, which the README explicitly notes is not necessarily the web server user. That is a real operational difference from mod_php style setups and it affects file ownership on anything PHP writes.

php-fpm can be started in several documented ways: through the init script at ~/.phpenv/versions/$VERSION/etc/init.d/php-fpm, through a systemd unit installed from ~/.phpenv/versions/$VERSION/etc/systemd/system/php-fpm.service, through a custom init script or unit you write yourself, or manually by running php-fpm with command-line arguments. The bundled configuration at ~/.phpenv/versions/$VERSION/etc/php-fpm.conf makes php-fpm listen on localhost:9000 by default. You can edit that file, replace it, or point php-fpm at another one with the --fpm-config (-y) argument.

For Apache there is an alternative: configure php-build to produce the libphp.so Apache extension, which the README says Apache can then find under ~/.phpenv/versions/$VERSION/libexec. The README points to external Apache and NGINX wiki articles for the server-side wiring rather than reproducing it, so expect to leave the phpenv documentation for that step.

## Where phpenv stops being the right answer

The clearest limitation is the build requirement. phpenv manages installations; php-build creates them by compiling from source. On a machine without a compiler toolchain, or in a container where you want PHP in seconds, that is the wrong shape of tool. A version manager that ships prebuilt binaries will get you running faster, and phpenv's README does not claim to compete on that axis.

Platform coverage is the second boundary. The README documents Homebrew for macOS and Linux and a git checkout for Unix-like shells. Nothing in it describes Windows, and the shell integration is built around POSIX shell initialization files. If your team develops on Windows, phpenv is not the tool the documentation prepares you for.

The third issue is upgrade and maintenance mechanics. Upgrading is documented as a git pull inside ~/.phpenv, and the installation instructions are a checkout you are invited to fork and contribute back to. There is no package-managed upgrade path described beyond the Homebrew install itself, and no documented rollback for a PHP version you have already built. The repository's last push was on 2026-07-30, with v1.1.0 released the same day and v1.0.0 before it in March 2024. That cadence is worth weighing if you depend on timely support for new PHP releases: the definitions that drive builds live in php-build, a separate project, so phpenv's own release rhythm is only part of the picture.

## Alternatives and how their approach differs

The README names its own lineage: phpenv was inspired by rbenv and ruby-build, and it transplants that model to PHP. The closest structural relative is phpbrew, which also targets multiple PHP builds on one machine but takes a different approach to the build step, and PHP version managers more generally. If you are evaluating phpenv against phpbrew, the distinction to check is who owns compilation and configuration: phpenv splits it out into the php-build plugin, so the phpenv repository stays a dispatcher while build logic, definitions and configure defaults live elsewhere. That separation is convenient when you want to swap or fork the build layer, and awkward when you want one project to document the whole path from source to running interpreter.

On macOS specifically, another category of tool bundles PHP with a local development environment rather than managing system-wide installs. Those are a different trade: less control over configure options, more convenience. phpenv sits at the control end of that spectrum. Its README is explicit that you can configure and install custom builds of the same PHP release directly from source, kept in your local .phpenv folder, which is the capability you would be giving up by choosing a bundled environment instead.

## Conclusion

Adopt phpenv if you already compile PHP from source, want several releases isolated under ~/.phpenv/versions/, and are comfortable with bash plugins. Do not adopt it if you need prebuilt PHP binaries, Windows support, or a managed upgrade channel: the README documents upgrades as a git pull inside ~/.phpenv, and the last push was on 2026-07-30. Before committing, verify that php-build can satisfy your configure options on your platform, and confirm your shell initialization file is the one your shell actually reads.

## FAQ

### What does phpenv do?

It manages multiple PHP installations and selects one per project directory. After it injects itself into your PATH, a shim reads the .php-version file in the current directory and calls the matching interpreter under ~/.phpenv/versions/.

### How do I install phpenv on macOS or Linux?

The README documents two routes: brew install phpenv/tap/phpenv from the phpenv tap, or a manual git clone into ~/.phpenv followed by adding ~/.phpenv/bin to PATH and eval "$(phpenv init -)" to your shell initialization file. Building PHP versions additionally requires the php-build plugin.

### Does phpenv download PHP versions for me?

No. The README states that each version is expected to be a directory resulting from a PHP installation, and that php-build is installed separately as a plugin to build versions with phpenv install.

### phpenv alternative

The README traces phpenv's design to rbenv and ruby-build, and phpbrew is the other commonly considered option for multiple PHP builds. The practical difference is that phpenv delegates compilation and configure options to the php-build plugin, keeping the main repository focused on version selection.

## Sources

- [Official README](https://github.com/phpenv/phpenv#readme)
- [Project repository](https://github.com/phpenv/phpenv)
- [Release notes](https://github.com/phpenv/phpenv/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/phpenv-phpenv
