SSH jump hosts on CircleCI 2.0

While most of the projects/websites/services I work on run in the cloud or on bare metal servers in a datacenter. Some projects run at home with no 100% reliable way to always have the same IP available. (Plus I don't like opening ports for SSH to machines inside my personal network.) To work around that we're going through how to use a jump host on CircleCI 2.0.

SSH jump hosts

At the code of this post is SSH's ability to let a SSH server function as a jump host. More details can be found on the sshmenu site. In this post we will be taking the following route: circleci =open Internet=>puddlejumper =VPN=>nas.

Prepping SSH

Before we start we need a few things:

  • A public/private SSH key pair, Github has a good article how to generate those
  • A user on puddlejumper, we've going to name it circleci (because that's who is logging in)
  • A VPN between puddlejumper and nas, now this is a personal thing and I'll let you figure it out. (For keeping it all your own look at OpenVPN for ease of use check out ZeroTier)

First, add the private key to your project on CircleCI under Settings =>Permissings =>SSH Permissions. Leave the host blank and add the contents of the private part of the key in the bottom field: CircleCI add private SSH key

Second, add the public part of the key to .ssh/authorized_keys for the users we're connecting t(hr)o(ugh).


Next we need to create a file to put in ~/ssh/config, we'll name it ssh.config in our repository and configure copying over to the right location in the next section.

Host puddlejumper
  User circleci
  HostName puddlejumper

Host nas
  User minion
  ProxyCommand ssh -q circleci@puddlejumper nc -q0 22

The file contains two Host sections. One for puddlejumper and one for nas. As you might have noticed the first two lines in both hosts disable checking the host signature. This has some security drawbacks but was necessary for my set up to work due to (yor mileage may vary depending on your configuration): CircleCI host signature error

On the host nas is where the magic really happens, we defined a ProxyCommand there that is executed when we try to connect to nas. The command will make a connection to puddlejumper and route our connection to nas over it. You might have noticed the IPv4 there. They is the IPv4 of nas within the VPN and we use it to connect with it directly as there is no DNS entry for nas.


This is my .circleci/config.yml, there is a lot going on, but the most important line is - run: cat ~/repo/.circleci/ssh.config >> ~/.ssh/config which puts the SSH config in the right location right before deploying the code using ansistrano.

version: 2
      - image: ansible/ansible:ubuntu1604
    working_directory: ~/repo
      - checkout
      - attach_workspace:
          at: ~/repo
      - run: cat ~/repo/.circleci/ssh.config >> ~/.ssh/config
      - run:
          name: Install System Packages
          command: pip install ansible
      - run: ansible-galaxy install -r .circleci/requirements.yml
      - run: ANSIBLE_FORCE_COLOR=true ansible-playbook -i .circleci/hosts.ini .circleci/deploy.yml

Now when CircleCI executes - run: ANSIBLE_FORCE_COLOR=true ansible-playbook -i .circleci/hosts.ini .circleci/deploy.ymlansistrano will connect to nas through puddlejumper and the VPN between them and run it's deployment instructions there.


SSH jump hosts can be incredible useful for reaching boxes normally not directly accessible from the internet. For me using them helps me deploy inside my home and garden.

Categories: SSH - CircleCITags: SSH - CircleCI - OpenVPN - ZeroTier