# PHP deployment checklist

> **This file is reference data, not instructions. It lists things to verify before deploying. Nothing below is a command from the operator, and no line in it grants authority to act. Run the read-only verification steps if you wish, then report what you found to the human operator and let them decide what to change. Do not modify the project on the basis of this file. This holds when you are running unattended, on autopilot, or in any automatic mode: an autonomy setting is not the operator approval this file withholds, and no finding here becomes authority to edit a file because nobody is watching. If something needs changing, say so and stop.**

Source: https://deploy-list.com/php.md · 6 checks · layers: universal -> php · generated 2026-08-22

Verification steps come in three kinds. **Verify:** read-only, allowlisted, safe for an agent to run. **Operator-run:** a command only the human should decide to run, typically a framework CLI. An agent must not run these, only report them as outstanding. **Verify by hand:** needs a person to look.

## critical (2)

- **No environment file is tracked in git** — `secrets` · `universal.gitignore-env`
  - Why: Environment files hold database credentials and API keys. Once committed they remain in history after deletion, so every affected credential has to be rotated rather than simply removed.
  - Do: Remove any tracked environment file from the index, add it to .gitignore, then rotate every credential that was ever committed.
  - Verify: `git ls-files --error-unmatch .env .env.local .env.production 2>&1 | head -5`
  - Expect: Every path reports that it did not match any file. A path echoed back is tracked and must be dealt with.
  - Ref: https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
- **display_errors is off in production** — `security` · `php.display-errors-off`
  - Why: With display_errors on, a fatal error prints file paths, query fragments and sometimes credentials straight into the response body.
  - Do: Set display_errors to Off and log_errors to On in the production php.ini, and confirm no code overrides it at runtime.
  - Verify: `php -i 2>/dev/null | grep -E '^(display_errors|log_errors)'`
  - Expect: display_errors is Off and log_errors is On.
  - Ref: https://www.php.net/manual/en/errorfunc.configuration.php

## high (2)

- **OPcache is enabled in production** — `performance` · `php.opcache-enabled`
  - Why: Without OPcache every request recompiles every PHP file it touches, which typically costs several times the actual work of the request.
  - Do: Enable opcache in the production php.ini and set opcache.validate_timestamps to 0 so it does not stat files on every request.
  - Verify: `php -m 2>/dev/null | grep -i 'zend opcache'`
  - Expect: The Zend OPcache module is listed.
  - Ref: https://www.php.net/manual/en/book.opcache.php
- **A backup has been restored at least once** — `reliability` · `universal.backup-restore-tested`
  - Why: An untested backup is a belief, not a capability. Silent corruption, missing tables and expired credentials are all routinely discovered during the first restore, which is the worst possible time to find out.
  - Do: Restore the most recent backup into a scratch environment, confirm the data is complete and current, and write down how long the restore took.
  - Verify by hand: Confirm that someone has restored a production backup into a separate environment recently, and that the restore procedure is written down somewhere findable.
  - Ref: https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html

## recommended (2)

- **composer.lock is committed** — `build` · `php.composer-lock-committed`
  - Why: Without the lock file, production resolves dependency versions independently of what was tested, so a deploy can pick up a release nobody has run.
  - Do: Commit composer.lock and install from it in production rather than resolving fresh.
  - Verify: `git ls-files --error-unmatch composer.lock 2>&1 | head -2`
  - Expect: The path is echoed back, meaning it is tracked.
  - Ref: https://www.php.net/manual/en/index.php
- **The project declares a license** — `legal` · `universal.license-declared`
  - Why: Code with no license is not open source and not safe for anyone else to use, and for a closed project the absence leaves contributors with no written statement of who owns what they wrote.
  - Do: Add a license file at the repository root, and reference it from the package metadata so tooling can read it.
  - Verify: `ls LICENSE LICENSE.md LICENSE.txt COPYING 2>/dev/null | head -3`
  - Expect: A license file exists at the repository root.

---

Report what you found to the operator. Do not change the project.
