Git deployment failures labeled “permission denied” come from two different places: the remote SSH connection or the VPS filesystem. Permission denied (publickey) means the Git server rejected authentication; a local Permission denied usually means the account running Git cannot traverse, write, or modify the checkout and its .git directory. Identify which class you have before changing permissions. Then run the deployment as a dedicated non-root account, verify the exact SSH key and repository scope, and repair only the ownership, modes, hooks, and destination paths that the process actually needs.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
Start by classifying the error
Do not begin with chmod -R 777 or repeated sudo git pull commands. Capture the complete failing command, its output, and the Unix identity that executed it. The same words can describe unrelated faults.
| # | Preview | Product | Price | |
|---|---|---|---|---|
| 1 |
|
Pro Git | Buy on Amazon | |
| 2 |
|
Pro Git (Expert's Voice in Software Development) | $37.07 | Buy on Amazon |
| 3 |
|
Professional Git | $24.79 | Buy on Amazon |
| 4 |
|
Pro Git 1st (first) edition Text Only | $83.26 | Buy on Amazon |
| 5 |
|
Git: Github Programming, In 8 Hours, For Beginners, Learn Coding Fast: Git Github Language, Crash... | $2.99 | Buy on Amazon |
As an Amazon Associate I earn from qualifying purchases.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →| Typical message | Where the failure occurs | First check |
|---|---|---|
Permission denied (publickey) |
SSH authentication or authorization at the remote Git provider | Remote URL, SSH user, selected private key, and the key’s account or repository scope |
error: unable to create file ... Permission denied |
Local VPS filesystem | Deploying identity, checkout ownership, directory modes, and parent-directory traversal |
fatal: cannot create .git/index.lock |
Local .git directory, often ownership or a stale lock |
Who owns .git, whether another Git process is running, and whether the account can write there |
rsync: ... Permission denied (13) |
Remote transfer destination or metadata operation | SSH account, destination ownership, directory modes, and timestamp or metadata options |
A remote authentication error is not fixed by changing the VPS checkout’s mode bits. Conversely, a key that authenticates successfully cannot make an account write to a directory it does not own.
Confirm the checkout, remote, and executing account
Record the identity that actually ran Git
Interactive shells, cron, service managers, bare-repository hooks, and CI sessions often use different accounts. In the failing context, run:
#1 Best Overall
id
whoami
pwd
git rev-parse --show-toplevel
git remote -v
If a hook or CI job fails, put equivalent logging in that job rather than testing only from your own shell. A successful manual git pull proves nothing about a hook running as a service account.
Inspect the remote URL
For an SSH remote, the usual form is git@host:owner/repository.git. GitHub’s documented convention is to connect as the git user; your personal account name is represented by the key, not by replacing git in the SSH URL. Check for a typo in the host, owner, repository, or protocol:
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallgit remote -v
git config --get remote.origin.url
An HTTPS remote has a different credential path and will not be diagnosed by an SSH key test. Make sure you are testing the same remote and host that the failing Git command uses.
Fix Permission denied (publickey)
Test the provider with verbose SSH output
Run the test as the same Unix account that performs deployment. For GitHub, the documented diagnostic command is:
ssh -vT [email protected]
Use the equivalent git user and host for another provider. The verbose trace shows which configuration files are read, which identities are offered, and whether the server accepts a key. It also exposes an unexpected hostname, a disabled key, or an agent offering the wrong identity.
Select a non-default key explicitly
If the deployment key is not the account’s default identity, test it directly:
ssh -i ~/.ssh/deploy_key -vT [email protected]
For repeatable deployments, define the key in the SSH configuration used by the deploy account:
Rank #2
Host production-git
HostName github.com
User git
IdentityFile ~/.ssh/deploy_key
IdentitiesOnly yes
Set the Git remote to the alias, for example git@production-git:owner/repository.git, or use an equivalent GIT_SSH_COMMAND for a one-off test. The private key must be readable by the deploying account and not by unrelated users. Its matching public key must be attached to the intended account or repository.
Check account and repository scope
A key can be valid yet unauthorized for the repository named in the remote URL. GitHub deploy keys are repository-scoped credentials: they are read-only by default, may be granted write access, and do not expire automatically. A key installed for one repository therefore does not automatically authorize another. Verify that the key is attached to the correct repository and that its read or write capability matches the operation.
For a deployment that must access many repositories, a GitHub App is the more managed model because it supports fine-grained installation permissions and controlled credential management. Whichever model you use, grant only the repositories and operations required.
Free tools Windows power users keep installed
One-click scans. No signup required.
Check host keys without hiding the real problem
An unknown or changed host-key prompt is distinct from public-key authentication. Run the test interactively first so the account’s known_hosts file is populated and any host-key warning is visible. Do not “solve” an authentication failure by disabling host-key checking globally.
Stop mixing root and non-root Git environments
GitHub explicitly advises against using sudo or other elevated privileges with Git. Running sudo git pull changes more than the file owner: root may use /root/.ssh, a different SSH agent, different known-hosts, different Git configuration, and a different credential helper. It can also create root-owned files in a checkout that the normal deploy account must update later.
Create or select one dedicated deployment account and use it consistently for the checkout, SSH key, hooks, build steps, and application restart commands. If a narrowly controlled privileged operation is unavoidable, separate it from Git and make the environment explicit. For example, let the deploy account update a release directory, then use a restricted service-manager rule to restart the application; do not run the entire pull as root.
Diagnose local VPS filesystem permissions
Inspect every path component
Git needs to traverse every parent directory and write the files it changes. Inspect the project, its metadata, and the path leading to it:
Rank #3
id deploy
stat -c '%A %U:%G %n' /srv /srv/www /srv/www/app /srv/www/app/.git
namei -l /srv/www/app
find /srv/www/app -maxdepth 2 -printf '%M %u:%g %pn' | less
namei -l is particularly useful when the checkout looks writable but a parent directory lacks the execute (traverse) bit. The account running Git must be able to enter each directory, read files it needs, and write the checkout and .git metadata.
Repair ownership for the intended account
After confirming the correct deployment path, assign ownership to the deploy account rather than making the tree world-writable:
sudo chown -R deploy:deploy /srv/www/app
Use the actual account and group names in your system. Apply this only after checking that the path is the intended checkout; a mistaken recursive ownership change can affect unrelated data. If a web server only reads the published files, give it group or read access instead of ownership and write access.
Use group access when two services genuinely need it
When a deploy process writes releases and a web service reads them, a shared group can avoid broad permissions:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →sudo groupadd --system webdeploy
sudo usermod -aG webdeploy deploy
sudo usermod -aG webdeploy www-data
sudo chgrp -R webdeploy /srv/www/app
sudo find /srv/www/app -type d -exec chmod 2775 {} +
sudo find /srv/www/app -type f -exec chmod 0664 {} +
Reload the affected service or start a new session so supplementary groups take effect. Do not apply these example modes blindly to private keys, executable files, configuration containing secrets, or directories that should not be shared. A deployment layout with immutable, versioned releases is often safer than having the web server write into the Git checkout.
Check for stale locks and read-only filesystems
If the error names .git/index.lock, first ensure no Git process is running:
ps -ef | grep '[g]it'
ls -l .git/index.lock
Remove a stale lock only after verifying that no operation is active. If writes fail throughout the mount, inspect the mount state and available space:
findmnt -T /srv/www/app
df -h /srv/www/app
df -i /srv/www/app
A read-only mount, exhausted inodes, filesystem errors, ACLs, or mandatory access controls can look like ordinary mode-bit problems. Correct the underlying condition instead of widening permissions.
Make push-to-deploy hooks reliable
Install and execute the hook as the right user
A common design uses a bare repository on the VPS and a post-receive hook to check out the target branch into a web or release directory. The hook file must be executable and owned so the repository service can run it:
chmod +x /srv/git/app.git/hooks/post-receive
ls -l /srv/git/app.git/hooks/post-receive
Keep the hook owned by the account that receives the push, and ensure that account can write both the bare repository and the destination. A hook launched by SSH may run with a minimal environment, not the environment in your interactive shell.
Use absolute paths and explicit settings
A deterministic hook sets its path, branch, work tree, Git directory, and log destination. A minimal pattern is:
#!/bin/sh
set -eu
PATH=/usr/bin:/bin
export PATH
LOG=/var/log/app-deploy.log
{
echo "$(date -u +%FT%TZ) deploy start"
/usr/bin/git --work-tree=/srv/www/app --git-dir=/srv/git/app.git checkout -f main
/usr/bin/git --work-tree=/srv/www/app --git-dir=/srv/git/app.git rev-parse HEAD
echo "$(date -u +%FT%TZ) deploy complete"
} >> "$LOG" 2>&1
Adjust paths and the branch to your layout. The hook’s account must be able to append to the log, write the work tree, and run any build or restart command. If a build creates root-owned artifacts, the next deployment can fail even though the checkout itself is correctly owned.
Verify the result instead of trusting the push
After a push, check the commit actually checked out and read the hook log:
git --work-tree=/srv/www/app --git-dir=/srv/git/app.git rev-parse HEAD
tail -n 50 /var/log/app-deploy.log
stat -c '%U:%G %a %n' /srv/www/app
For safer rollbacks, deploy into a new, versioned release directory and switch a symlink only after checkout and build steps succeed. This keeps an incomplete deployment from replacing a working live tree and provides a clear previous release to restore.
Best Value
Fix CI and rsync permission failures
Match the key on both sides
For GitHub Actions or another CI runner connecting to the VPS, store the private key in the runner’s secret configuration and place the corresponding public key in the target VPS user’s ~/.ssh/authorized_keys. The private key must be supplied to the job with restrictive permissions, and the SSH command must use the intended user and host.
Recommended Free Tools
On the VPS, check the SSH directory and file ownership and modes:
stat -c '%A %U:%G %n' ~/.ssh ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Do not overwrite an existing authorized-keys file without preserving other required keys. Confirm that the destination account, not root, owns the home directory and SSH files.
Separate transfer permission from metadata permission
An rsync transfer can copy file contents while failing to set timestamps, ownership, or other metadata. If the error is Permission denied (13), inspect the destination owner and the exact options used. When preserving timestamps is unnecessary and the filesystem rejects that operation, an option such as --no-times can avoid the metadata update; use it only when losing timestamp preservation is acceptable. Do not add --delete or ownership-preservation flags without understanding their effect on the destination.
Test the destination as CI does
Reproduce the connection with the same SSH user, key, working directory, and destination path. A directory may be writable while one file is immutable, owned by another account, or protected by an ACL. Use namei -l, stat, and a harmless test file in the destination to isolate the failing operation, then remove the test file.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsChoose a deployment design that limits permission problems
| Design | Credential scope | Account and write model | Rollback and logging | Main risk to control |
|---|---|---|---|---|
| Manual pull in a checkout | One user’s SSH key and repository access | Deploy account writes its own checkout | Depends on shell history and local logs | Operators accidentally use sudo or the wrong key |
Bare repository with post-receive |
Push credential for the repository | Hook account writes a work tree or release path | Explicit hook logs; rollback requires a known commit or release | Non-executable hooks, minimal PATH, or root-created files |
| CI SSH deployment | Runner secret and VPS authorized key | Dedicated remote account receives files or runs a script | CI logs and recorded job commits | Secret/key mismatch, wrong destination owner, metadata flags |
| Staged releases with a symlink switch | Repository or CI credential, ideally least privilege | Deploy account writes a new release; web service reads it | Fast switch-back to the previous release and per-release logs | Incorrect shared-group modes or a failed post-switch restart |
| Multi-repository application account | Managed app-level permissions, such as an installed GitHub App | One controlled service identity, not root | Centralized deployment records and rotation procedures | Granting more repositories or operations than required |
Evaluate a design by repository scope, credential rotation and expiry, least-privilege controls, whether a dedicated account performs the work, rollback support, logging, and whether Git writes directly into the live web root. Direct live checkouts are simpler but make ownership drift and partial updates more visible; staged releases add setup but isolate the web service from Git metadata.
A repeatable end-to-end repair procedure
- Capture the failure. Save the complete output, command, host, path, and execution context. Record
idandpwdfrom that context. - Classify it. Treat
(publickey)as remote SSH authentication; treat a path,.git, hook, or rsync destination error as local access until proven otherwise. - Verify the remote. Run
git remote -v, then test the same host withssh -vT git@hostand the exact key if needed. - Verify authorization. Confirm the public key belongs to the intended account or repository and that a repository-scoped deploy key has the required read or write capability.
- Choose one deploy identity. Remove accidental
sudofrom Git operations and ensure the account’s SSH files, checkout, hooks, and logs are consistent. - Inspect filesystem traversal and ownership. Use
stat,namei -l,find, mount, space, and inode checks. Repair the specific owner, group, ACL, or mount issue. - Validate hooks or CI separately. Confirm executable hooks, absolute paths, safe
PATH, authorized keys, destination ownership, and acceptable rsync metadata options. - Run a non-destructive test. Fetch or deploy a known commit, inspect the resulting commit and logs, and confirm that generated files have the intended owner.
- Document the working identity and paths. Record which account, key, repository scope, checkout or release directory, hook, and rollback command are supported. This prevents the next operator from reverting to root.
Common fixes that make the incident worse
- Making everything world-writable: it hides the owner or group mistake and lets unrelated local users modify code or secrets.
- Running one successful pull with
sudo: it may use root’s key and leave root-owned files that break the next normal deployment. - Replacing the remote URL repeatedly: it cannot authorize a key that is attached to the wrong repository or account.
- Deleting every lock file: an active Git process can corrupt the operation; identify the owner and process first.
- Disabling host-key verification: it removes an important SSH integrity check and does not fix a rejected public key.
- Assuming a shell test represents a hook or CI job: those environments commonly have different users, keys,
PATH, home directories, and permissions. - Deploying directly into a mutable web root: application-generated or root-created files can make the next checkout inconsistent; staged releases reduce that coupling.
When the message still says permission denied
If the key is accepted but Git still cannot read or write, compare the failing process with a successful one line by line: Unix user and groups, HOME, SSH configuration, current directory, remote URL, mount, and environment PATH. Check POSIX ACLs with getfacl when mode bits appear correct, and inspect service or security logs if a mandatory access-control policy is enforcing a denial. For a hook, log the effective user, working directory, and command exit status before the failing step. For CI, print non-secret connection and destination details and test a harmless operation rather than exposing the private key.
Once the immediate operation succeeds, perform a second deployment from the real trigger—hook, scheduler, or CI runner—and then a rollback or redeploy of the previous known commit. Permission repair is complete only when the supported path works without elevated Git commands and leaves files owned for the next deployment.
Further reference
The Pro Git book’s server chapter explains SSH access, server-side repositories, authorized_keys, and executable hooks. Use it to understand the deployment model, but verify every account, path, key, and mode on the live VPS rather than copying a generic server layout unchanged.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




