← Back to blog

The Right Way to Deploy Private GitHub Repos to Your VPS

The Right Way to Deploy Private GitHub Repos to Your VPS

Part 2 of my VPS Deployment Series — If you haven't secured your firewall yet, check out Part 1: Don’t Lock Yourself Out – Enabling UFW.

Deploying code from a private repository to a VPS is something a lot of developers sort of know how to do — but most tutorials either rely on personal SSH keys or Personal Access Tokens. That works, but it gives your server much more access than it actually needs.

In this guide you’ll learn how to set up a repository-specific SSH deploy key for your VPS so you can:

  • Clone your private repository securely
  • Pull updates without storing personal credentials on the server
  • Keep production access scoped narrowly and safely

This method is slightly more advanced than a basic SSH clone, but it’s well worth it if you care about security and clean operations.


What you’ll learn

  • How to create repository-specific SSH deploy keys
  • Why deploy keys are more secure than personal credentials
  • How to configure SSH for multiple GitHub identities
  • The right way to structure your deployment directory
  • How to maintain and rotate deploy keys safely

Time required: ~10–20 minutes
Skill level: Beginner to intermediate (comfortable with SSH and basic Linux)
What you’ll need: SSH access to your VPS with sudo privileges and administrator access to your GitHub repository

Prerequisite: This tutorial assumes you already have SSH access to your server. If you haven't set up your firewall yet, I recommend checking out my guide on securing SSH with UFW first.


Why This Matters

There are several ways to authenticate to GitHub from a server:

  • Using your personal SSH key (risky — it ties the server to your personal account)
  • Using a Personal Access Token (works, but provides overly broad access)
  • Using a Repository-Specific Deploy Key (ideal — scoped access)

Deploy keys follow the principle of least privilege: the server gets just enough access to pull the code it needs, no more.


Step 1 — Prepare Your Deployment Environment

First, create a dedicated deployment user.

Rather than cloning as root or your own account, it’s better to use a dedicated user. By dedicated user, I mean a Linux system user created specifically to run and deploy a single application or repository — not a human login account.

sudo adduser --system --group yourappname
sudo mkdir -p /opt/yourappname
sudo chown yourappname:www-data /opt/yourappname

This ensures:

  • Deployment files are owned by a service user
  • Permissions stay clear and restricted

Why /opt and not /home, /srv, or somewhere else?

TL;DR: /opt makes it immediately obvious what is system-owned and what you deployed yourself.

In Linux, different top-level directories have different intentions. Using the right one is about keeping your server understandable six months from now.

What /opt actually is

/opt stands for optional software.

Historically (and still today), it’s meant for:

  • software not managed by the OS package manager
  • applications you deploy yourself
  • self-contained services that live outside the base system

That describes a manually deployed web application perfectly.


Why /opt is a good fit for deployed applications

Putting your application in /opt/yourappname has several advantages:

1. Clear separation from the operating system

Your app is not part of the OS.

  • /bin, /usr, /lib → operating system
  • /opt → things you added

That means:

  • OS upgrades won’t touch your app
  • you won’t accidentally overwrite system files
  • backups and restores are simpler

2. Clear separation from user home directories

Using /home for production apps is tempting, but misleading.

Home directories are meant for:

  • human users
  • shell config
  • personal files

A deployment user is not a human.

By using /opt:

  • there’s no expectation of interactive usage
  • no confusion about “who owns what”
  • no accidental exposure via home-directory defaults

3. Predictable layout for multi-app servers

If you later deploy more applications, /opt scales cleanly:

/opt/
├── yourappname/
├── anotherapp-1/
├── anotherapp-2/

Each app:

  • has its own directory
  • has its own system user
  • can have its own service, ports, and permissions

This makes audits easier, troubleshooting faster, and future automation simpler.

4. Works well with permissions and service users

The combination /opt/yourappname owned by yourappname:www-data means:

  • the deploy user controls the code
  • the web server can read what it needs
  • root is not involved in daily operations

That’s exactly what you want on a production server.


Why not /srv?

You could use /srv, and some people do.

However:

  • /srv is traditionally used for data served directly (FTP, NFS, static web roots)
  • many distros barely use it in practice
  • fewer people recognize it instantly

/opt is more universally understood for application installs.


Step 2 — Generate a Repository-Specific SSH Deploy Key

Switch to the deployment user:

sudo su - yourappname

Generate a new SSH key specifically for this repository:

ssh-keygen -t ed25519 -C "github-deploy-key-yourappname" -f ~/.ssh/id_ed25519_deploy_yourappname

This creates a modern ed25519 key (more secure than RSA) with a descriptive filename so it doesn’t conflict with other keys.

When prompted for a passphrase, you can leave it empty for automated deployments — security comes from strict file permissions and the key being limited to a single repository.

Display the public key:

cat ~/.ssh/id_ed25519_deploy_yourappname.pub

Copy the entire output — you’ll paste it into GitHub next.


Step 3 — Register the Deploy Key on GitHub

  1. Open your repository on GitHub
  2. Go to Settings → Deploy keys
  3. Click Add deploy key
  4. Paste the public key you generated
  5. Give it a sensible title (e.g. VPS Deploy Key)
  6. Do NOT enable write access — this key should be read-only

This gives your VPS the ability to read (clone/pull) this repository only.


Step 4 — Configure SSH on the Server

On the yourappname user, create or edit the SSH config:

nano ~/.ssh/config

Add:

Host github-deploy
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_deploy_yourappname
    IdentitiesOnly yes

This tells SSH to use the correct key only for this host alias.

Test it:

ssh -T github-deploy

If it says you’ve successfully authenticated (but can’t shell), you’re good.


Step 5 — Clone the Repository

If you’re not logged in as the deployment user, switch to it first:

sudo su - yourappname

Then clone the repository:

cd /opt/yourappname
git clone git@github-deploy:yourusername/yourappname.git

That command uses the custom github-deploy host alias — so Git uses the specific deploy key.


Step 6 — Verify Permissions and Security

Make sure the .ssh directory is locked down:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519_deploy_yourappname
chmod 644 ~/.ssh/id_ed25519_deploy_yourappname.pub
chmod 600 ~/.ssh/config

Verify:

ls -la ~/.ssh/

You should see only the expected key, config, and correct permissions.


Step 7 — Pulling Updates Later

When you need to update the app:

sudo su - yourappname
cd /opt/yourappname
git pull origin main

No passwords, no tokens — just the deploy key doing its job.

If you have a systemd service (e.g. Gunicorn), restart it after the pull:

sudo systemctl restart yourappname

Step 8 — Key Rotation and Maintenance

Over time, you may want to:

  • Rotate the deploy key
  • Revoke older deploy keys from GitHub
  • Monitor your repository’s key list for unused entries

Because this key is repo-specific, revoking it only affects this deployment — not your entire GitHub account.


Common Pitfalls & Quick Checks

Wrong remote URL?

git remote -v

It should show:

origin git@github-deploy:yourusername/yourappname.git (fetch)
origin git@github-deploy:yourusername/yourappname.git (push)

It should not show [email protected] — that would use the wrong key.

SSH connection issues?

Run verbose SSH to debug:

ssh -vT github-deploy
  • -v → verbose output (shows which key and config SSH uses)
  • -T → disables shell allocation (GitHub does not provide shell access)

Successful output looks like:

Hi yourusername/yourappname! You've successfully authenticated, but GitHub does not provide shell access.

Wrapping Up

Using a repository-specific deploy key gives you a clean, secure way to pull private code onto a server without exposing broad access credentials.

The principle is simple: give each server exactly the access it needs — nothing more.

If this deploy key is ever compromised, you can revoke it on GitHub without affecting your other repositories or your personal access.

If you found this helpful, you might also want to check out the other articles in this series: