Automating code deployments from GitHub to an Ubuntu server requires secure, passwordless authentication. Using SSH keys instead of passwords blocks brute-force intrusions while allowing continuous integration pipelines to push updates smoothly. If you're setting up a CI/CD pipeline or managing production infrastructure, getting your keys configured correctly on both ends is essential.
This guide walks through generating secure cryptographic keys, transferring them to your Ubuntu server, registering public keys with GitHub, and configuring permissions tightly. You'll learn how to avoid common permission traps that break automated workflows and how to test the connection end-to-end.
Generating and Managing SSH Keys
Before touching your server, you need a strong cryptographic key pair on your local machine or build runner. Avoid older RSA algorithms with small bit lengths; instead, use modern Ed25519 keys, which offer better security and shorter lengths.
Open your terminal and run the following command to generate a new key pair, replacing the email with your own:
ssh-keygen -t ed25519 -C "your_email@example.com"
When prompted to save the file, press Enter to accept the default location (~/.ssh/id_ed25519). Next, enter a secure passphrase. While automated deployment servers often require unencrypted keys to run unattended, human operator keys should always use a strong passphrase.
Once generated, you'll have two files:
- Private key (
id_ed25519): Kept strictly secret on your local machine or stored securely in GitHub Actions secrets. - Public key (
id_ed25519.pub): Shared with remote servers and GitHub.
Configuring the Ubuntu Server
Log into your Ubuntu server via SSH using your existing administrative credentials. Before importing any keys, make sure your SSH daemon configuration enforces best practices.
Open the SSH daemon configuration file using nano:
sudo nano /etc/ssh/sshd_config
Verify or adjust the following directives to lock down access:
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
Save the file and restart the SSH service to apply changes:
sudo systemctl restart ssh
Next, ensure the target deployment user has the proper .ssh directory structure set up. If you're using a dedicated deployment user named deploy, run these commands:
sudo su - deploy
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Incorrect directory permissions are the number one reason SSH key authentication fails. The .ssh folder must be restricted to user-only read, write, and execute permissions (700), and authorized_keys must be readable and writable only by the owner (600).
Importing the Public Key
You need to place your local or deployment public key into the authorized_keys file on the Ubuntu server. If you have terminal access to both machines, you can use ssh-copy-id from your local machine:
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your_server_ip
If you're copying the key manually, open the authorized_keys file on the server:
nano ~/.ssh/authorized_keys
Paste the contents of your id_ed25519.pub file into a new line, save, and exit.
For GitHub Actions workflows, you also need to make sure the server recognizes GitHub's own SSH keys if your server needs to pull private repositories during deployment. Add GitHub to your server's known hosts file by running:
ssh-keyscan github.com >> ~/.ssh/known_hosts
Connecting GitHub Actions to Ubuntu
To let GitHub push code or trigger deployment scripts on your Ubuntu server, you must store your private key inside your GitHub repository secrets.
- Go to your repository on GitHub.
- Click Settings > Secrets and variables > Actions.
- Click New repository secret.
- Name the secret
SSH_PRIVATE_KEYand paste the entire contents of your private key (id_ed25519) into the value field. - Add additional secrets for your server IP (
SERVER_IP) and username (SERVER_USER).
Next, create a workflow file in your repository at .github/workflows/deploy.yml. Use an action like appleboy/ssh-action to execute commands on your server securely:
name: Deploy to Ubuntu
on:
push:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Execute Remote SSH Commands
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-app
git pull origin main
npm install
pm2 restart my-app
According to Ubuntu Server Documentation, keeping your package lists updated and running firewall rules via UFW ensures that your SSH port remains protected against unauthorized scanning while automated pipelines do their work.
Common Mistakes and Troubleshooting
Even with careful setup, minor configuration errors can break deployments. Watch out for these frequent pitfalls:
- Wrong file permissions: If Ubuntu's SSH daemon detects that
authorized_keysis group-writable or world-readable, it silently rejects the key for security reasons. Always re-verifychmodsettings. - Line ending corruptions: Copying private keys through web interfaces or poorly configured text editors can introduce Windows carriage returns (
\r). Ensure your secret values contain clean Unix line endings. - Passphrase on CI keys: If you generated your deployment key with a passphrase, GitHub Actions will hang waiting for user input because it cannot type the passphrase interactively. Always use unencrypted keys specifically dedicated to automation runners.
- SELinux or AppArmor interference: If strict mandatory access control policies are active on your server, they might block the SSH daemon from reading user home directories. Check
/var/log/auth.logfor specific denial messages.
Frequently Asked Questions
Can I use the same SSH key for multiple servers?
While technically possible, it's a security risk. If a single server is compromised, an attacker gains access to every other environment sharing that key. Generate unique key pairs for each deployment target.
How do I revoke an SSH key if it's compromised?
Simply open the authorized_keys file on your Ubuntu server and delete the specific public key line associated with the compromised device or pipeline. The connection drops immediately.
Why do I get a "Permission denied (publickey)" error?
This error usually means the server doesn't recognize your key, permissions on .ssh are too open, or you're logging in with the wrong username. Run your ssh command with the -v (verbose) flag to trace exactly which authentication methods failed.
Should I disable password authentication entirely?
Yes, once your SSH keys are tested and working reliably. Disabling password authentication stops automated botnets from hammering your server login prompt with brute-force password guessing attacks. Refer to the OpenSSH Guidelines for more details on hardening remote access.