
September 11, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
You change a Bash alias on your laptop, tweak PHP-FPM pool settings on staging, and patch an Apache vhost on production — then forget what you changed. That drift costs hours. When you manage dotfiles and server config with Git, every shell profile, editor setting, and web-server snippet has a history you can diff, roll back, and replay on a new machine. On real client projects I often handle dev, deploy, and server maintenance in one role, so a single Git workflow for personal tooling and production config beats scattered notes and ad hoc SSH edits. This guide walks through a practical setup for PHP/Laravel developers running Ubuntu 22/24 servers.
.gitignore, and deploy server configs with tagged releases plus a post-merge reload script.The pattern fits naturally alongside the Ubuntu server setup workflow and the Git habits covered in Git rebase vs merge. If you maintain multiple Laravel sites on shared EC2 infrastructure, versioned config is as important as versioned application code.
What does it mean to manage dotfiles and server config with Git?
Dotfiles are hidden config files in your home directory: .bashrc, .gitconfig, .vimrc, and tool-specific folders like .config/nvim. Server config lives under /etc: Nginx site files, Apache vhosts, PHP-FPM pools, UFW snippets, and systemd drop-ins.
Git gives you three things manual copying cannot. You get a full change history with blame. You get branches for experiments. You get tags that mark known-good production states.
In practice I split repos by risk and audience. Personal dotfiles live in one private repo. Shared server baselines live in another. Application deploy configs — Deployer, GitLab CI — stay inside each project repo. That separation keeps a mistaken git push from exposing production paths on a public dotfiles repo.
Version control is not a backup tool. Pair Git with the backup patterns in Ubuntu server backup strategies and automated server backup setup. Git tracks intent. Backups capture live state including databases and uploaded files.
What belongs in Git vs what does not
- Track: Shell profiles, Git aliases, editor settings, SSH
config(without private keys), Nginx/Apache templates, PHP-FPM pool defaults, UFW rule scripts, fail2ban jails, cron templates. - Never track: Private keys,
.envfiles, TLS private keys, database passwords, API tokens,/etc/shadow, or anything under/var/wwwthat users upload. - Track as templates: Use
.env.exampleorpool.conf.examplewith placeholder values. Fill real values on the host from a secrets store or encrypted file outside Git.
Run secrets scanning in Git and CI with gitleaks on every push. One leaked Deployer SSH key in a dotfiles repo can compromise every site on a shared EC2 box.
How do you set up a Git-backed dotfiles repository?
The most reliable pattern for personal dotfiles is a bare repository in your home directory. Your working tree is your actual home folder. This avoids moving files into a subdirectory and breaking paths.
Step 1: Create the bare repo on your laptop
- Create a private remote on GitHub, GitLab, or a self-hosted instance.
- On your machine, initialise the bare repo:
git clone --bare git@gitlab.com:you/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
git config --local status.showUntrackedFiles no
echo 'alias dotfiles="/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME"' >> ~/.bashrc
source ~/.bashrc
The alias lets you run dotfiles status instead of passing --git-dir every time. Official Git documentation covers bare repositories at git-scm.com/docs/git-clone.
Step 2: Add files selectively
dotfiles add .bashrc .gitconfig .config/starship.toml
dotfiles commit -m "Initial shell and Git config"
dotfiles push origin main
Add a root .gitignore inside the bare repo's index for paths you never want:
# inside ~/.dotfiles as a tracked file at repo root
.DS_Store
.cache/
.local/share/nvim/swap/
*.secret
.env
id_rsa
id_ed25519
Step 3: Bootstrap a new machine
git clone --bare git@gitlab.com:you/dotfiles.git ~/.dotfiles
echo 'alias dotfiles="/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME"' >> ~/.bashrc
source ~/.bashrc
dotfiles checkout main
On first checkout Git may refuse to overwrite existing files. Move conflicting defaults aside, then rerun checkout. That is normal on fresh Ubuntu installs.
GNU Stow as an alternative
GNU Stow creates symlinks from a structured repo into your home directory. It shines when you maintain optional packages: stow nvim on dev machines, skip it on minimal servers. Stow documentation lives at gnu.org/software/stow. I use bare repos for speed on laptops and Stow when a repo grows past twenty packages.
Generate strong unique passwords for service accounts with the password generator tool. Store them in a password manager, not in Git.
Which approach should you use for dotfiles and server config?
Teams ask whether bare repos, Stow, Chezmoi, or Ansible is "best." Each solves a different slice of the problem. Pick based on machine count, team size, and how often you reprovision servers.
| Approach | Best for | Learning curve | Server config fit |
|---|---|---|---|
| Git bare repo | Solo dev, fast personal sync | Low | Poor alone — home dir only |
| GNU Stow | Modular dotfiles packages | Low | Fair with /etc via custom targets |
| Chezmoi | Templated dotfiles, secrets encryption | Medium | Limited — dev-focused |
| Ansible playbooks | Multi-server baselines, PHP stacks | Medium–high | Excellent — idempotent /etc deploy |
| Git + symlink deploy script | Small fleet, full control | Low–medium | Excellent for 1–5 Ubuntu web servers |
For Laravel hosting stacks I often combine Git-tracked config trees with Ansible or a thin shell deploy script. Read Ansible playbooks for PHP server provisioning and automate server setup with Ansible when you outgrow manual symlinks. Ansible handles drift correction. Git remains the source of truth.
Chezmoi and yadm are fine for personal machines. They do not replace root-level web-server management on production. Keep that boundary clear.
How do you version Apache, Nginx, and PHP-FPM server config with Git?
Never edit /etc/nginx/sites-available/app.conf in place without a Git copy elsewhere. I mirror production config into a private repo under a predictable tree, then deploy with symlinks or rsync --dry-run first.
Recommended repo layout for web servers
server-config/
├── README.md
├── bin/
│ ├── deploy-config.sh
│ └── validate-nginx.sh
├── common/
│ ├── nginx/snippets/ssl-params.conf
│ ├── php/8.3/fpm/pool.d/www.conf.example
│ └── ufw/rules.sh
├── hosts/
│ ├── staging.example.com/
│ │ ├── nginx/site.conf
│ │ └── php-fpm/pool.conf
│ └── prod.example.com/
│ ├── nginx/site.conf
│ └── php-fpm/pool.conf
└── secrets/ # gitignored — never committed
└── prod.example.com.env
This mirrors how I maintain sister sites on shared EC2 with Deployer 7 and GitLab CI. Application code deploys through one pipeline. Server baseline config deploys through another, less frequent pipeline.
PHP-FPM and multi-version PHP
Ubuntu servers often run PHP 8.3 and 8.4 side by side. Track pool files per version:
common/php/8.3/fpm/pool.d/www.conf.example
common/php/8.4/fpm/pool.d/www.conf.example
After deploy, reload the correct FPM socket. Wrong socket paths break Laravel apps silently — the site loads but sessions fail. I have debugged this on production Laravel deployments more than once.
Apache vs Nginx paths
Track site configs under host folders. For Apache, map to sites-available and enable with a2ensite. For Nginx, symlink into sites-enabled. The comparison in Nginx vs Apache performance and config helps you standardise snippets across both.
Include systemd unit drop-ins if you override OOM scores or restart policies. See systemd manage services on Linux for reload vs restart decisions.
How do you deploy dotfiles and server config to production safely?
Treat config deploys like application deploys: tagged releases, a checklist, and a rollback path. Never git pull on production as root without reviewing the diff first.
A minimal deploy script
#!/usr/bin/env bash
set -euo pipefail
CONFIG_ROOT="/opt/server-config"
HOSTNAME="$(hostname -f)"
RELEASE_TAG="${1:?Usage: deploy-config.sh v2026.09.01}"
cd "$CONFIG_ROOT"
git fetch origin
git checkout "$RELEASE_TAG"
install -d /etc/nginx/snippets
ln -sf "$CONFIG_ROOT/common/nginx/snippets/ssl-params.conf" /etc/nginx/snippets/ssl-params.conf
ln -sf "$CONFIG_ROOT/hosts/${HOSTNAME}/nginx/site.conf" /etc/nginx/sites-available/app.conf
nginx -t
systemctl reload nginx
systemctl reload "php8.3-fpm"
echo "Deployed ${RELEASE_TAG} for ${HOSTNAME}"
Run validation before reload. nginx -t and apachectl configtest catch syntax errors. PHP-FPM accepts php-fpm8.3 -t on Ubuntu when the package ships the test flag.
Integrate with CI/CD
GitLab CI can lint Nginx config on every merge request:
lint:nginx:
image: nginx:1.27
script:
- nginx -t -c $CI_PROJECT_DIR/test/nginx.conf
Keep application pipelines separate from infrastructure config pipelines. A broken .bashrc should not block a Laravel release. A broken Nginx snippet should not ride along with a frontend asset build.
For permission issues after deploy, see how to stop Git from tracking file permissions. Config files in /etc often need root ownership. Track content in Git. Apply ownership in the deploy script with chown root:root.
Rollback in under two minutes
- Identify the last good tag:
git tag --sort=-creatordate | head - Checkout that tag in
/opt/server-config - Re-run symlinks and validation
- Reload services
- Document the incident in your ticket system
If you need to recover lost Git commits locally, Git reflog recovery applies to dotfiles repos too.
What are common mistakes when you manage dotfiles and server config with Git?
Most failures are operational, not Git syntax problems. These recur on client projects and my own machines.
Committing secrets "just once"
Removing a password in the next commit does not erase it from history. Rotate the credential immediately. Use BFG Repo-Cleaner or git filter-repo if the repo ever went public. Prevention beats cleanup.
Editing production directly
SSH into the box, fix Nginx, close the terminal — the fix exists only in production. Force a habit: edit in Git first, deploy second. Emergency hotfixes get cherry-picked back into the repo within 24 hours.
Mixing personal dotfiles with production server config
Your .vimrc does not belong in the same repo as client vhost files. Split repos by audience and access control. Give contractors access to application code, not your personal shell aliases or unrelated client vhosts.
Forgetting service reload after config change
PHP-FPM and Nginx do not auto-reread files on every request. Opcache adds another layer after deploy. On Laravel stacks I reload PHP-FPM after symlink swaps so opcache picks up changed .env permissions and autoload paths.
Ignoring drift on long-lived servers
A server running two years accumulates manual edits. Schedule a quarterly audit:
diff -ru /etc/nginx/snippets/ssl-params.conf /opt/server-config/common/nginx/snippets/ssl-params.conf || true
Reconcile diffs into Git or delete orphan files. Pair this with server hardening for Ubuntu web servers and CIS benchmarks for server hardening.
UFW rules belong in version control as executable scripts, not screenshots. Document the intent in comments. Apply with UFW firewall rules for web servers as reference.
Skipping monitoring after config deploy
After reload, watch error logs and health checks for five minutes. Tools covered in server monitoring with Netdata and Ubuntu server monitoring guide catch 502 spikes faster than client emails.
For broader security context in Nepal hosting environments, read how to secure your website and server in Nepal. Version control supports security. It does not replace it.
Key Takeaways
- Use a private Git bare repo for personal dotfiles; bootstrap new laptops with
git clone --bareand a shell alias. - Keep server config in a separate repo with
common/baselines andhosts/overrides for each Ubuntu web server. - Never commit secrets — track
.exampletemplates and inject live values on the host outside Git history. - Deploy config with Git tags, run
nginx -tbefore reload, and keep a one-command rollback path. - Audit
/etcagainst Git quarterly to eliminate silent drift on long-lived production boxes. - Combine Git config management with Ansible when you manage more than a handful of servers.
People Also Ask
Should dotfiles be a public or private Git repository?
Keep dotfiles private unless you are certain no path, alias, or host hint exposes internal infrastructure. Public dotfiles repos are fine for generic editor settings. Production hostnames, VPN endpoints, and client paths should stay in a private repo with restricted access.
Can you use the same Git repo for dotfiles and /etc/nginx config?
Technically yes, but separate repos scale better. Personal machine settings change often and belong to you. Server config changes on a release cadence and may involve client staff. Split repos simplify permissions and CI pipelines.
How do you handle machine-specific settings in a shared dotfiles repo?
Use conditional includes in .bashrc, Git config includeIf blocks, or host-named branches. GNU Stow lets you enable packages per machine: stow git everywhere, stow nvim only on dev laptops. Chezmoi templates work if you prefer .tmpl files over shell conditionals.
Does Git replace configuration management tools like Ansible?
No. Git stores desired state. Ansible applies it idempotently and can install packages Git should not manage. For one or two servers, Git plus a deploy script is enough. For a growing fleet, pair versioned config with Ansible playbooks and keep Git as the authoritative text source.
Build a repeatable config workflow before the next server emergency
When you manage dotfiles and server config with Git, rebuilding a laptop or rolling back a bad Nginx change stops being a multi-hour rescue job. Start with a bare repo this week. Add your shell and Git config. Then extract one production vhost into a private server-config repo with a tagged deploy script.
If you want help standardising Ubuntu hosting baselines for Laravel, WordPress, or multi-site EC2 setups, see Linux system administration services and support and maintenance services. For full-stack delivery including deploy pipelines, review the Adventure Third Pole Trek portfolio case and other work on the portfolio page.
Need hands-on help auditing drift on an existing box or wiring GitLab CI to lint Nginx config? Contact us with your stack details — PHP version, web server, and host count are enough to start.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

