I'm trying to set up automatic CI/CD deployment of my frontend via GitHub Actions using SSH to connect to a Hetzner cloud VM (Shared vCPU).
Everything works except for one issue related to the firewall configuration.
For security reasons, I have configured the firewall to only allow SSH (port 22) from my own IP address.
However, I also want GitHub Actions to deploy to my server automatically via SSH. The problem is that GitHub Actions uses a large and dynamic pool of IP addresses, which are not practical to manually whitelist in the firewall.
What I’ve tried so far:
ssh-keygen -t ed25519 -C "user@computer" -f C:/Users/Admin/.ssh/hetzner-key
SSH_KEY
)~/.ssh/authorized_keys
on the Hetzner VMappleboy/scp-action
and appleboy/ssh-action
to transfer and run deployment scriptsWhen the firewall restricts SSH to my own IP, GitHub Actions fails with the following error:
drone-scp error: error copy file to dest: ***,
error message: dial tcp ***:22: i/o timeout
2025/08/05 18:24:48 error copy file to dest: ***,
error message: dial tcp ***:22: i/o timeout
How can I securely allow GitHub Actions to SSH into my Hetzner VM without having to manually update the firewall with all GitHub IP ranges?
Is there a common practice that solves this issue?
I’m looking for a secure and scalable solution.
My GitHub Actions workflow (deploy.yml)
name: Deploy Frontend to Hetzner
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t frontend-app .
docker save frontend-app > frontend.tar
- name: Copy to Server
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.SERVER_IP }}
username: root
key: ${{ secrets.SSH_KEY }}
source: "frontend.tar"
target: "/root/"
- name: Deploy Container
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_IP }}
username: root
key: ${{ secrets.SSH_KEY }}
script: |
docker load -i /root/frontend.tar
docker stop frontend-container || true
docker rm frontend-container || true
docker run -d \
--name frontend-container \
-p 80:80 \
-p 443:443 \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /var/www/html:/var/www/html \
frontend-app
GitHub Actions publish their IP ranges via an API. You would need to scrape this and auto-configure your firewall.
curl -s https://api.github.com/meta | jq -r '.actions[]'
NOTE: There are both IPv4 and IPv6 ranges.
Alternatively, you could switch to a pull model of deployment from you SSH push model. You could publish your deployment artifacts as a release on the repo. Then you could setup a webhook from GitHub on your VM to listen for repo events such as release published, pull and deploy your artifacts to the VM.
You could publish you image to GitHub Container registry as well and then deploy that image on event.
You can also periodically check the Container registry from the VM looking for new version and then deploy.