Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk11 min

How to Fix Git Deployment “Permission Denied” Errors on a Linux VPS

Learn why Git deployments fail with permission denied on Linux VPS servers and follow a precise fix for SSH keys, deploy-key scope, file ownership, hooks, CI runners, and rsync transfers.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Git deployment failures on a Linux VPS usually belong to one of two different problems: the Git server rejected your SSH credentials, or the VPS account running Git cannot read or write the local checkout. The exact wording tells you which path to investigate. Permission denied (publickey) is an SSH authentication or repository-access failure; a local Permission denied while updating files points to ownership, mode bits, directory traversal, mounts, or the account running the deployment.

Capture the complete failing command, its full error, and the identity that ran it before changing permissions. Then follow the matching procedure below. Avoid repeatedly applying chmod or running Git as root: those actions often hide the real cause and can make later deployments fail under the normal deploy account.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

First classify the error

What you see What it normally means Start here
Permission denied (publickey) The remote SSH server rejected the key, user, host, or repository authorization. Check the remote URL, connect as the git user, and inspect key selection with verbose SSH output.
error: cannot open ...: Permission denied, unable to create file, or a failure writing .git The local deployment account cannot traverse a directory or write the checkout or Git metadata. Inspect the account, ownership, mode bits, parent directories, mounts, and service context on the VPS.
Permission denied (13) during an rsync-style transfer The remote destination, metadata operation, or transfer account lacks the required permission. Check the destination owner and directory permissions, then test timestamp and metadata options.

Do not treat these messages as interchangeable. Changing the VPS checkout’s ownership cannot fix a rejected SSH key, and replacing an SSH key cannot give a local service account write access to /var/www.

Record the failing identity and environment

A deployment may run from an interactive shell, a bare-repository hook, cron, a system service, or a CI runner. Each context can have a different user, home directory, PATH, SSH agent, and Git configuration. Run these checks in the same context that fails whenever possible:

whoami
id
pwd
env | sort
git remote -v
git status

For a hook or service, write the output to a temporary log or invoke a small diagnostic script as that account. Record the absolute checkout path and the branch being deployed. A command that succeeds in your shell but fails in a hook is usually an identity or environment difference, not a random Git problem.

Fix remote SSH authentication and repository access

Verify the remote URL and required user

From the deployment checkout, inspect the configured remote:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
git remote -v

An SSH remote normally uses the provider’s host with the git user, for example git@host:owner/repository.git. GitHub documentation says connections should use the git account, not your personal username. A remote that points to the wrong host, an old repository path, or an HTTPS URL while you are troubleshooting SSH can send you down the wrong branch of the diagnosis.

Test the same host outside Git

Use verbose SSH output against the host shown by git remote -v:

ssh -vT [email protected]

Replace the hostname with your Git provider when it is not GitHub. The verbose trace shows the configuration files read, identities offered, and the point at which the server rejects the connection. A successful authentication test does not prove that the repository path is correct, but a Permission denied (publickey) result confirms that the key or account authorization must be fixed before Git can fetch.

Make non-default keys explicit

If the deployment uses a key other than the default identity, test that exact file:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ssh -i ~/.ssh/KEY-FILE -vT [email protected]

For a repeatable deployment, put the intended host, user, and IdentityFile in the deploy account’s SSH configuration rather than relying on an interactive agent. Check that the private key exists for that account and that the corresponding public key is attached to the intended repository or account. Do not assume a key loaded in your personal shell is available to a hook, cron job, or CI session.

Check key file ownership and permissions

The deploy account must be able to read its private key, while other users should not be able to read it. Inspect the key directory and files:

ls -ld ~/.ssh
ls -l ~/.ssh
stat ~/.ssh/KEY-FILE

Correct ownership first. Then use restrictive modes appropriate to your distribution, commonly a private key readable only by its owner and an SSH directory accessible only by that owner. If SSH reports that a key is too open, follow the mode it requests rather than broadening access.

Confirm the key’s scope

A deploy key is tied to one repository. GitHub documents that deploy keys are read-only by default, may be granted write access, and do not expire automatically. Therefore, a key that works for one repository can still be rejected when the remote points to another repository. Verify both the repository association and the required operation: a read-only key can clone and pull but cannot push.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For deployments spanning multiple repositories or requiring centrally managed, fine-grained permissions, GitHub recommends GitHub Apps as a more controlled alternative. Whichever credential type you choose, grant only the operations and repositories the deployment actually needs, and define a rotation or revocation procedure.

Do not mix sudo git with normal-user Git

GitHub explicitly advises against using sudo or other elevated privileges with Git. Running one command as root changes more than file ownership: Git may read /root/.ssh, use a different SSH agent and known-hosts file, load a different Git configuration, and create root-owned files in the checkout.

Choose a dedicated non-root deployment account and run the complete pull, checkout, build, and hook process as that account. If a narrowly controlled privileged action is unavoidable, separate it from Git and make its identity and environment explicit. Check the resulting file ownership immediately so the next deployment account can still update the working tree and .git.

Inspect local VPS permissions before changing them

Identify every path Git must traverse

Git needs execute (traverse) permission on every parent directory, read access to the repository, and write access to the working tree and Git metadata for operations such as pull, merge, checkout, and fetch. Inspect the project, its .git directory, and each parent:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
stat /path/to/project
stat /path/to/project/.git
namei -l /path/to/project
find /path/to/project -maxdepth 2 -printf '%M %u:%g %pn' | sort

namei -l is especially useful when the final directory looks writable but an upper-level directory blocks traversal. Check whether the checkout is a separate mount, a read-only filesystem, a network share, or a container volume with its own UID/GID mapping:

findmnt -T /path/to/project
mount | grep -F /path/to/project
df -h /path/to/project

A full disk, read-only mount, ACL, or security policy can produce a permission-style failure even when the basic mode bits appear correct.

Correct ownership for the account that deploys

Determine the actual account with whoami or id, then compare it with the owner and group shown by stat. If a previous root deployment created the checkout, transfer ownership to the intended deploy account rather than making the tree world-writable:

sudo chown -R deploy:deploy /path/to/project

Substitute your real account and group, and review the path carefully before running a recursive command. If a web server only needs to serve the generated files, give that service read or group access instead of granting it write access to the repository. Keep .git writable only by the process that must update it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check ACLs and special restrictions

Traditional owner/group/mode output is not the whole permission model. If the modes look right but access still fails, inspect extended ACLs and attributes:

getfacl -p /path/to/project
lsattr -d /path/to/project /path/to/project/.git

Remove or adjust an ACL only when you understand which service needs access. An immutable attribute, a restrictive parent ACL, or a security profile can override the permissions you see in a simple directory listing.

Make a push-to-deploy hook reliable

A common design receives a push into a bare repository and runs a post-receive hook to update a working tree. Pro Git documents server-side repositories, SSH keys, authorized_keys, and executable hooks; a typical hook invokes:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
git --work-tree=/path/to/site --git-dir=/path/to/bare.git checkout -f main

Install and execute the hook as the deploy account

  1. Place the hook in the bare repository’s hooks/post-receive file.
  2. Use absolute paths for the bare repository, work tree, build tools, and restart commands.
  3. Set a safe PATH because hooks do not necessarily inherit your login shell environment.
  4. Make the hook executable with chmod +x /path/to/bare.git/hooks/post-receive.
  5. Ensure the hook’s owner is the account receiving the push and deploying the files.
  6. Log the received branch, commit, command exit status, and deployment output to a location that account can write.

Keep the target branch explicit. If the hook checks out main but the push updates another branch, the hook can appear to run successfully while deploying nothing. Ensure application restart or build commands have the required permissions and cannot leave root-owned artifacts in the next release.

Verify the result after each test push

After a controlled push, check the latest deployed commit and inspect the hook log:

git --work-tree=/path/to/site --git-dir=/path/to/bare.git log -1 --oneline
stat /path/to/site
sudo tail -n 100 /var/log/your-deploy.log

Use the actual log path configured for your system. If the commit changed but generated assets or a service restart failed, the Git checkout permission is fixed and the remaining problem is in the build or runtime step.

Fix GitHub Actions, CI, and rsync deployments

For a CI runner connecting to the VPS, the private key must be stored in the runner’s secret configuration, and the matching public key must be present in the target VPS user’s authorized_keys. The runner’s account is not your local account, so test with the same key and destination user that the workflow uses.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate the SSH destination

ssh -i /path/to/ci-key -v [email protected]

Confirm the host key is handled deliberately, the destination username is correct, and the key’s file permissions satisfy the SSH client. On the VPS, inspect:

id deploy
stat /home/deploy/.ssh/authorized_keys
namei -l /home/deploy/.ssh/authorized_keys

Make sure the home directory, .ssh directory, and authorized_keys are owned by the target account and not writable by unrelated users.

Diagnose rsync permission errors

When rsync reports Permission denied (13), check the destination directory owner, group, and traversal rights first. Then determine whether rsync is failing while creating a file, deleting an old file, preserving timestamps, or changing another metadata field. If the filesystem rejects timestamp updates, test the transfer with options such as --no-times where that trade-off is acceptable. Do not permanently suppress metadata errors until you know which metadata the application relies on.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choose a deployment design that limits permission failures

Design Credential scope Process identity Permission and recovery considerations
Manual pull on the VPS Usually one repository credential for the deploy account. An operator’s shell or a dedicated deploy user. Simple to inspect, but easy to mix users or leave root-owned files; document the exact account and remote.
Bare repository with a hook SSH access to the receiving account and repository. The account owning the bare repository and hook. Fast push-to-deploy flow; requires executable hooks, absolute paths, explicit branch handling, and logs.
CI runner over SSH or rsync A runner secret plus the VPS user’s authorized key. The remote user specified by the workflow. Centralized logs and repeatability; destination ownership and metadata options must be tested.
Staged releases with a switch Credential scope can be limited to the deployment repository. A dedicated account writes release directories and changes the active pointer. Separates immutable releases from the live web root and makes rollback easier, but requires careful ownership of the release and switch paths.
Multi-repository integration Repository-scoped deploy keys may become numerous. A service account or application identity. GitHub Apps offer more managed, fine-grained access than piling write-capable keys onto one server.

Evaluate a design by credential scope, rotation and expiry options, least-privilege controls, dedicated-user execution, rollback and logging, and whether it writes directly into the live web root or stages a release first. The most convenient design is not automatically the safest if every step runs as root or every repository shares one unrestricted key.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common fixes that make the problem worse

  • Using chmod -R 777: this hides ownership mistakes and lets every local user modify code or deployment credentials. Correct the owner, group, ACL, or mount instead.
  • Running one successful pull with sudo: it can create root-owned files and prove only that root has access, not that deployment is configured correctly.
  • Copying a personal private key to the server: this expands the blast radius and makes rotation difficult. Use a repository-scoped deploy key or managed application identity.
  • Changing SSH keys when the error is local: a failure writing .git/index.lock or a working-tree file is on the VPS, not at the Git provider.
  • Fixing only the final directory: missing traverse permission on a parent directory still blocks access.
  • Assuming an interactive shell equals a hook or CI job: those contexts may have different users, home directories, agents, and PATH values.

A repeatable end-to-end repair procedure

  1. Save the evidence. Copy the full error, command, timestamp, branch, destination path, and output of whoami and id.
  2. Classify the failure. Route Permission denied (publickey) to SSH diagnostics; route local file or .git errors to filesystem diagnostics.
  3. Confirm the remote. Run git remote -v, then test the same host with ssh -vT git@host and the intended key.
  4. Confirm credential scope. Check that the key belongs to the expected account or repository and that it has read or write capability appropriate to the operation.
  5. Confirm the local identity. Run the failing operation as the dedicated deploy account, not root, and record its home directory and environment.
  6. Trace the path. Use namei -l, stat, findmnt, and disk checks on the checkout, .git, and all parents.
  7. Repair narrowly. Change ownership or group access for the intended account, fix an incorrect mount or ACL, and make hooks executable. Avoid world-writable modes.
  8. Retest the exact automation. Trigger the same hook, CI job, cron command, or rsync invocation that failed; do not substitute an interactive command.
  9. Verify the deployed commit and logs. Confirm the expected commit is present, generated files have the correct owner, and restart or build steps completed.
  10. Document the boundary. Record which account owns the repository, which credential reaches the Git provider, which paths are writable, and how to rotate or revoke access.

When to escalate beyond ordinary permissions

Escalate to system administration or hosting support when the path is on a read-only or failing filesystem, UID/GID mappings differ across containers, an ACL or security policy denies access despite correct ownership, or the SSH server rejects a key that is demonstrably offered and correctly associated. Preserve the verbose SSH trace and filesystem inspection output; those details identify whether the remaining control point is the Git provider, SSH daemon, operating system policy, or storage layer.

The Pro Git book’s server chapter is a useful reference for SSH server repositories, authorized_keys, and hooks, but it cannot replace checking the live VPS account, path, and logs. A deployment is fixed only when the same non-root identity used in production can authenticate to the intended repository and update the intended files without elevated privileges.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.