Putting WordPress under version control by adding everything is how the accident starts. Core, uploads and configuration all live in one tree, so the first job is to draw the commit boundary. This is the rule set we actually use when moving client sites to Git.
The boundary, in one picture
One question decides everything: if this file were lost, could code recreate it? If yes (core, vendors, caches) — ignore it. If no (the theme and plugins you wrote) — commit it. The single exception is secrets: irreplaceable, and still never committed.
Why wp-config.php comes first
It holds the database password and all eight auth salts in plain text. The moment the repository goes public — or a private repo leaks a single credential — everything needed to forge sessions for the whole site ships with it. We have inherited sites where a plaintext DB password sat committed in version control for years. Keep only wp-config.sample.php in the repo; real values are created on the server at deploy time.
A committed secret is not erased by a delete commit. It stays in history, already copied into every clone. The only real response is to revoke and reissue the values themselves.
A working .gitignore
# Secrets — never
wordpress/wp-config.php
.env
# Core — reproducible from a version number
wordpress/wp-admin/
wordpress/wp-includes/
wordpress/wp-*.php
wordpress/index.php
# User artefacts and runtime
wordpress/wp-content/uploads/
wordpress/wp-content/upgrade/
wordpress/wp-content/cache/
# Build and tooling
node_modules/
*.sql
*.log
Pinning core without committing it
Record the version number instead of the files. A deploy script running wp core download --version=X.Y reproduces the same tree on any server. Committing core bloats the repository and turns every core update into a thousand-file diff that buries the review of your own code.
If it is already committed
- Secrets — rotate the DB password and reissue salts now (
api.wordpress.org/secret-key/1.1/salt/). Cleaning history is the second problem. - Large files — remove them from history with
git filter-repo; plan for a full re-clone if you collaborate. - From today — add the .gitignore, then
git rm -r --cachedto stop tracking without touching the server.
Drawing this boundary is half of a deployment workflow. The other half — shipping exactly what staging verified — is what our work process documents, and the full service is described in the optimization program.