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

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Use a bare Git repository as the central remote on a Linux VPS, keep every working tree outside that repository, and authenticate administrators or automation with verified SSH keys. For deployments, check out each pushed commit into a separate release directory, validate it, then switch the live symlink atomically. This layout keeps Git storage, deployable files, and the public web root independent, making rollback and recovery practical.

Choose the repository architecture first

A VPS can play two different roles: it can host your central Git remote, or it can receive code from a hosted source such as GitLab and deploy that code. Both designs work, but they assign collaboration, security, and recovery responsibilities differently.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Decision area Self-hosted bare remote Hosted source with VPS deployment
Repository location The canonical repository is stored on the VPS, for example /srv/git/project.git. The canonical repository remains with the hosted provider; the VPS keeps deployment clones or release checkouts.
Collaboration and review You must provide your own access controls and any review workflow. The provider supplies collaboration, review, and project administration features.
Operational burden You maintain SSH access, repository permissions, hooks, monitoring, and backups. You still maintain the VPS, but provider availability and credentials become dependencies.
Authentication scope Developer keys authenticate directly to the VPS service account. A project-scoped deploy key can give the VPS read-only access, with write permission only when required.
Recovery responsibility You are responsible for backing up all Git objects, refs, hooks, and configuration. You must protect deployment configuration and local releases, while the hosted provider generally remains the source repository.
Deployment audit trail Pushes and hook logs are yours to record and retain. Repository events are recorded by the provider, while VPS-side deployment logs remain your responsibility.

The rest of this guide shows both patterns, then applies the same safe deployment model to either one.

Why the central remote should be bare

A bare repository contains Git’s object database, refs, and configuration but has no checked-out working tree. That makes it suitable as a shared remote: pushes update refs without competing with files that someone may be editing or serving. A normal clone, by contrast, has a working tree and is intended for inspecting, changing, testing, or deploying files.

#1 Best Overall

Create the bare repository outside the public web root. The repository directory is storage for Git, not a document root and not a place from which a web server should serve application files. A separate clone or release checkout can be used to build and deploy a particular commit.

Never make the bare repository itself the live site. A web server pointed at its internal directories could expose source, configuration, or Git metadata, and a deployment that modifies a live working tree can leave a half-updated application visible to users.

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

Prepare the Linux VPS

Install and record the required packages

Install the Git and OpenSSH packages supplied by your Linux distribution. Record the installed versions and the distribution release in your operations notes. Repeating a deployment or rebuilding a replacement VPS is easier when the toolchain is known rather than assumed.

Create a dedicated service account

Create a git service account for a self-hosted remote. It should own the repository and have only the directory permissions needed for Git operations. Disable interactive login for this account unless a specific administrative procedure requires it; developers should authenticate to the account only to invoke Git’s server-side commands.

Keep repository storage, release directories, logs, and the live web root separate. For example:

  • /srv/git/project.git — the bare central repository.
  • /srv/releases/project-<commit> — immutable or disposable release checkouts.
  • /srv/www/project-current — a symlink to the active release.
  • /var/log/project-deploy/ — deployment and hook logs, protected from public access.

Restrict the network surface

Allow SSH only as required by your firewall and network policy. If the VPS also serves HTTP or HTTPS, those web services should not grant access to /srv/git. File ownership and mode bits are a second line of defense, not a substitute for network and web-server restrictions.

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

Set up SSH authentication correctly

SSH uses two keys: a public key and a private key. The private key stays on the developer or automation host and must never be copied into the repository, committed to source control, or placed in shell history. Install only the corresponding public key in the authorized account on the VPS.

Create a key pair on the client or CI host

When supported by the clients you operate, prefer an ED25519 key. Protect it with a passphrase for interactive use, or store it in a restricted secret-management system for automation. An SSH agent can hold an unlocked key during a controlled session so scripts do not need to expose the passphrase.

Verify the VPS host key before using it

Before the first push or clone, verify the VPS host-key fingerprint through a trusted, separate channel. Add the verified key to the client’s known-hosts database. Production automation should use a pinned host-key process rather than accepting an unknown key automatically. This prevents an operator from confusing a changed or intercepted host with the intended VPS.

Limit what an authorized key can do

For a self-hosted remote, the key used by a developer should be authorized only for the intended Git account and repository permissions. For automated deployment, use a separate key from human keys and grant the smallest possible scope. Review and revoke keys when people, systems, or projects change.

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

Build a self-hosted bare remote

Initialize the repository on the VPS

After creating the service account and parent directory, initialize the bare repository as that account:

sudo install -d -o git -g git -m 0750 /srv/git/project.git
sudo -u git git init --bare /srv/git/project.git

The exact account name, group, path, and permissions depend on your distribution and access model. Check that the service account can read and write the repository while unrelated users cannot.

Add the remote on a development machine

From an existing local clone, add the VPS as origin and push the branch you intend to publish:

git remote add origin git@vps.example:/srv/git/project.git
git push -u origin main

Replace the hostname, path, and branch with your values. Confirm the remote before pushing:

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

The last command should show the refs that exist on the VPS. If the project uses another default branch, configure and document that branch rather than assuming main.

Test from a clean clone

Clone the repository into a temporary directory with the same kind of SSH identity used by a developer or build host. Check out the expected branch and inspect the files. This catches incorrect ownership, an incorrect repository path, missing authorized keys, and a default-branch mismatch before deployment automation is added.

Use a hosted source with a VPS deploy key

If the source of truth is GitLab, the VPS normally pulls rather than receives developer pushes. Create a project deploy key on the VPS and prefer read-only access for this purpose. Deploy keys are intended for non-human interaction with a project; grant write access only if the VPS genuinely must push tags, commits, or other refs.

Keep the deployment identity separate

Generate a key dedicated to this project and deployment purpose. Do not reuse a personal key or a key shared by unrelated applications. Store the private half with restrictive permissions and make the corresponding public key visible only in the project’s deploy-key configuration.

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

Clone into a release location

A first checkout can use a path such as:

git clone git@gitlab.example:group/project.git /srv/releases/project-<commit>

The angle-bracket text denotes the commit-specific directory name; replace it with an actual commit identifier in your script. Pin the checkout to the exact commit or tag that passed your checks instead of silently deploying whichever branch happens to move next.

For subsequent releases, fetch the required ref into a temporary directory, check out the target commit, run validation, and only then make it live. A failed fetch or checkout must leave the current release untouched.

Design a safe push-to-deploy flow

A Git hook is a program triggered by a Git event. The hook should not turn the repository directory into the web root. Its job is to coordinate a controlled release process.

Place and enable the hook

For a self-hosted bare repository, a server-side receive hook can react after a push is accepted. GitLab’s server-hook documentation likewise requires hook files to be executable and placed in the configured hook directory; the event triggers the hook. Apply the same operational discipline to either environment: explicit ownership, executable mode, a known interpreter, and a protected directory.

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

Validate the event and input

  • Accept only the expected repository and branch or ref.
  • Use the commit ID supplied by Git, not an untrusted filename or commit message, to identify the release.
  • Quote shell variables and avoid evaluating commit messages, paths, or ref names as shell code.
  • Use absolute paths for Git, service-manager, and application commands so the hook does not depend on an interactive shell’s environment.
  • Record the commit ID, start time, result, and error output in a protected log.

Build each release separately

Create a new release directory for the target commit. Check out or export the commit there, install dependencies using the application’s locked files, and run syntax, migration, asset, or health checks appropriate to the application. Do not delete the current release when any preparation step fails.

Switch the live version atomically

After validation succeeds, update a symlink such as /srv/www/project-current to point to the new release. A rename of a prepared symlink is atomic on the same filesystem, so readers see either the old complete release or the new complete release rather than a directory being filled file by file. Reload or restart the service only when the application requires it, and make that action explicit in the hook.

Keep rollback available

Retain at least one previous known-good release. If health checks or monitoring detect a failure, point the live symlink back to that release and reload the service. The rollback procedure should be documented and executable independently of the failed hook.

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.

Example deployment sequence

  1. Receive or select a commit. A push to the self-hosted remote, or a controlled pull from the hosted source, identifies one immutable commit.
  2. Create a temporary release path. Use a unique directory under the releases parent and ensure the deployment user owns it.
  3. Fetch and check out exactly that commit. Avoid deploying an implicitly changing branch tip.
  4. Install and validate. Use locked dependency versions, compile assets if needed, run tests and configuration checks, and verify required environment settings without printing secrets.
  5. Run an application health check. Test locally or against a private listener before exposing the release publicly.
  6. Switch the symlink. Replace the current-link atomically only after every check passes.
  7. Reload the service and verify. Check service status, logs, and an application endpoint appropriate to your system.
  8. Record the outcome. Log the commit, release path, operator or automation identity, checks, and exit status.
  9. Clean up safely. Remove old releases only after the retention rule preserves the rollback versions you require.

Permissions and least privilege

The account that accepts Git pushes does not automatically need permission to modify every application file or restart every service. Separate responsibilities where practical:

  • Let the git account own the bare repository and receive Git traffic.
  • Let a deployment account own release directories and perform checkouts and validations.
  • Grant only a narrowly defined service reload permission if a restart is required.
  • Keep environment files and deployment secrets outside the repository, readable only by the service that needs them.
  • Ensure the web-server account can read the active release but cannot rewrite the repository or deployment scripts.

If a hook must cross account boundaries, use a tightly constrained service-manager rule or wrapper command. Do not give the hook unrestricted administrative access merely to avoid diagnosing one permission error.

Backups and restoration

A repository backup is more than a copy of a working directory. For a self-hosted remote, preserve Git objects, refs, repository configuration, server hooks, service files, deployment scripts, and the metadata needed to recover keys and secrets. Also include the release and web-server configuration that determines how a commit becomes live.

Store backups off the VPS

Keep backup copies on storage independent of the VPS. A disk snapshot on the same machine does not protect against deletion, filesystem corruption, or loss of the host. Protect backup access as carefully as production access.

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

Define a recovery objective

Choose how much recent history and deployment configuration you can afford to lose and how quickly service must return. The retention schedule is an operator decision; document it rather than assuming that one recent archive is sufficient.

Perform a restore drill

Test restoration on a separate path or replacement host. Verify that the restored repository can be cloned, refs are present, hooks have the expected permissions, deployment scripts can create a release, and the service can be pointed to the restored files. A backup is not usable until this process succeeds.

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

Troubleshooting common failures

“Permission denied (publickey)”

Confirm that the client is using the intended private key, the matching public key is installed for the correct account, and the VPS account can read its authorized-keys file. Check host-key verification separately; a trusted host key does not authenticate a user. For automation, inspect the job’s SSH configuration rather than assuming it inherits your interactive agent.

“Repository not found” or a path error

Inspect git remote -v and compare the SSH path with the actual bare repository location. On a self-hosted server, the path is interpreted by the SSH account, so ownership and parent-directory traversal permissions matter. Test a clean clone using the same account and key.

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

The hook never runs

Check that the hook is in the repository’s configured hooks directory, has executable permissions, uses a valid interpreter, and is triggered by the event you expect. Read the protected hook log and capture standard error. A hook that exits early without logging is difficult to operate.

The site shows a partial deployment

This usually means the web root points directly at a directory being modified. Move checkouts to a new release directory, validate them there, and switch a symlink only after completion. Keep the previous release until the new one has passed health checks.

A failed deployment removed the working version

Change the hook so cleanup happens only after a new release is live and verified. Restore the previous release by repointing the live symlink, then inspect the failed release and logs before retrying.

Operational checklist

  • Use a bare repository for a central self-hosted remote.
  • Keep bare storage, release checkouts, logs, and the public web root in separate paths.
  • Create a dedicated service account and disable unnecessary interactive login.
  • Prefer ED25519 SSH keys where supported; protect private keys and install only public keys remotely.
  • Verify and pin the VPS host key before unattended operations.
  • Use a read-only, project-scoped deploy key for pull-only hosted-source deployments.
  • Validate hook refs and inputs; never execute untrusted names or messages as shell code.
  • Deploy immutable commit-specific releases and switch the live symlink atomically.
  • Keep at least one previous release and monitor hook exit status.
  • Back up repository objects, refs, hooks, service files, and deployment metadata off-host.
  • Prove the backup with a restoration drill against your stated recovery objective.

Frequently Asked Questions

Can I use a normal clone as the central repository on the VPS?

Use a bare repository for the central remote. A normal clone has a working tree and is better suited to inspection, testing, or deployment; keeping it as the shared push target can create conflicts with checked-out files.

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.

Should a VPS deploy key have write access?

No, not for a pull-only deployment. Make the project-scoped deploy key read-only and grant write permission only when the VPS must intentionally push changes.

Where should the live website point?

Point it at a separate release directory, commonly through a symlink such as /srv/www/project-current. Do not point the web server at the bare repository.

How do I make a Git hook safe for production?

Make it executable and place it in the configured hook directory, validate the expected ref, use the exact commit ID, quote inputs, run with least privilege, log failures, and switch releases only after checks pass.

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.

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