You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让GitHub Actions安全SSH访问端口22受防火墙限制的Hetzner虚拟机?

问题

我正在通过GitHub Actions结合SSH,将前端项目自动CI/CD部署到Hetzner云虚拟机(共享vCPU)。目前所有配置正常,仅防火墙配置存在问题:出于安全考虑,我把防火墙设置为仅允许自身IP访问SSH(22端口),但希望GitHub Actions能自动通过SSH部署到服务器。但GitHub Actions使用动态IP池,无法手动把所有IP加入白名单。

已尝试的操作:

  • 在Windows系统执行命令ssh-keygen -t ed25519 -C "user@computer" -f C:/Users/Admin/.ssh/hetzner-key生成SSH密钥对;
  • 将私钥添加到GitHub Secrets(命名为SSH_KEY);
  • 将对应的公钥添加到Hetzner虚拟机的~/.ssh/authorized_keys文件中;
  • 临时允许所有IPv4/IPv6访问防火墙时,部署可正常运行;
  • 使用appleboy/scp-action和appleboy/ssh-action传输并运行部署脚本。

防火墙限制仅自身IP访问SSH时,GitHub Actions部署失败,报错:

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

我的GitHub Actions工作流文件(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
解决方案

方案1:动态更新Hetzner防火墙规则

利用GitHub Actions Runner的临时出口IP,通过Hetzner Cloud API将该IP临时加入防火墙白名单,部署完成后自动移除,全程无需手动操作。

操作步骤:

  1. 在Hetzner Cloud控制台创建API Token,权限至少包含firewall:read和firewall:write,将Token存入GitHub Secrets(命名为HETZNER_API_TOKEN)。
  2. 修改GitHub Actions工作流,新增步骤获取Runner出口IP、添加临时防火墙规则,部署结束后移除规则:
- name: 获取Runner出口IP
  id: get_ip
  run: echo "IP=$(curl -s https://api.ipify.org)" >> $GITHUB_OUTPUT

- name: 将Runner IP加入Hetzner防火墙
  run: |
    curl -X POST "https://api.hetzner.cloud/v1/firewalls/{你的防火墙ID}/actions/add_rules" \
      -H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \
      -H "Content-Type: application/json" \
      -d '{
        "rules": [
          {
            "direction": "in",
            "protocol": "tcp",
            "port": "22",
            "source_ips": ["${{ steps.get_ip.outputs.IP }}/32"]
          }
        ]
      }'

# 保留原有的Build、Copy、Container部署步骤

- name: 从防火墙移除Runner IP
  if: always()
  run: |
    curl -X POST "https://api.hetzner.cloud/v1/firewalls/{你的防火墙ID}/actions/remove_rules" \
      -H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \
      -H "Content-Type: application/json" \
      -d '{
        "rules": [
          {
            "direction": "in",
            "protocol": "tcp",
            "port": "22",
            "source_ips": ["${{ steps.get_ip.outputs.IP }}/32"]
          }
        ]
      }'
  • if: always()确保无论部署成功或失败,都会清理临时IP规则,避免白名单残留。

方案2:专用部署用户+SSH命令限制

创建仅用于部署的专用用户,限制该用户只能执行指定的部署命令,同时在防火墙和SSH配置中区分管理员用户与部署用户的访问权限,兼顾安全与自动化需求。

操作步骤:

  1. 在服务器创建专用部署用户:useradd -m deploy
  2. 生成新的SSH密钥对,将公钥添加到deploy用户的~/.ssh/authorized_keys,并添加命令限制(仅允许执行指定部署脚本):
command="bash /home/deploy/deploy-script.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... deploy@github-actions
  1. 编写/home/deploy/deploy-script.sh,仅包含部署所需的命令(如加载Docker镜像、重启容器等),禁止其他操作。
  2. 修改服务器/etc/ssh/sshd_config,限制管理员用户仅允许自身IP访问,部署用户允许所有IP:
Match User root
    AllowUsers root@你的IP/32

Match User deploy
    AllowUsers deploy@*
  1. 在防火墙中开放22端口给所有IP,依赖SSH配置的用户访问限制保证安全。

方案3:反向SSH隧道

让Hetzner虚拟机主动发起SSH连接到GitHub Actions Runner,建立反向隧道,无需开放服务器22端口给外部,通过Runner的端口完成部署通信。

操作步骤:

  1. 在GitHub Secrets中存储服务器的SSH私钥(用于服务器主动连接Runner)。
  2. 在GitHub Actions工作流中启动临时SSH服务,然后触发服务器主动连接并建立反向隧道:
- name: 启动临时SSH服务
  run: |
    mkdir -p ~/.ssh
    echo "${{ secrets.SERVER_SSH_KEY }}" > ~/.ssh/id_ed25519
    chmod 600 ~/.ssh/id_ed25519
    ssh-keygen -f ~/.ssh/id_ed25519 -y > ~/.ssh/id_ed25519.pub
    cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
    sudo systemctl start ssh

- name: 触发服务器建立反向隧道
  run: |
    ssh -o StrictHostKeyChecking=no root@${{ secrets.SERVER_IP }} -R 2222:localhost:22 "sleep 300" &

- name: 通过隧道传输文件
  uses: appleboy/scp-action@v1
  with:
    host: localhost
    port: 2222
    username: root
    key: ${{ secrets.SSH_KEY }}
    source: "frontend.tar"
    target: "/root/"

# 后续部署步骤同理,将host改为localhost,port改为2222

内容的提问来源于stack exchange,提问作者coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 13:44:53