How to create a jumpbox to securely connect to your compute instances running on Civo?
Learn how to secure Civo Compute instances by placing them on a private network and accessing them through a single jumpbox using firewall rules and SSH ProxyJump.
Written by
Founder at KubeNine
Written by
Founder at KubeNine
Stop exposing every Civo instance: put them behind a private network and one jumpbox
When you spin up a Compute instance on Civo, it gets a public IPv4 address by default. That's convenient for a quick test box. It's a liability for a fleet. Every instance with a public IP is an instance with an internet-facing SSH port, and "we'll lock the firewall down later" is how port 22 ends up in everyone's logs.
There's a cleaner pattern, and Civo gives you everything you need for it out of the box: drop your workload instances onto a private network with no public IP at all, and reach them through a single jumpbox (bastion host) that is the only machine exposed to the internet. Humans SSH into the jumpbox; the jumpbox is the only door to everything else.
If you've built this on AWS, you're bracing for the annoying part: a NAT gateway so your private instances can still run apt update. On Civo you can relax. Instances on a private network get outbound internet access automatically — there's no NAT gateway to provision, configure, or pay for. Your private boxes can pull packages on day one while staying completely unreachable from the outside.
Here's the whole thing, built with the Civo CLI.
Architecture overview

Three pieces:
- A private network that all the instances share and route over internally.
- Backend instances with
--publicip=none. They have no internet-facing surface. They still get outbound internet automatically. - One jumpbox with a public IP, sitting on the same private network, locked down so only you can SSH in.
Two firewalls enforce the boundary: one that exposes only the jumpbox's SSH port to your IP, and one that lets the backends accept SSH only from inside the private network.
Prerequisites
You need the Civo CLI installed and authenticated:
civo apikey show # Confirm you're pointed at the correct accountcivo sshkey ls # List your uploaded SSH keys
If you haven't uploaded an SSH key yet:
civo sshkey create my-key --key ~/.ssh/id_ed25519.pub
Everything below assumes a single region (set yours with civo region current <code> if needed).
Step 1: Create the private network
civo network create secure-net
Confirm it and note its CIDR — you'll need it for the backend firewall rule:
civo network ls

I'll use 192.168.1.0/24 from here on — substitute whatever Civo assigned you.
Step 2: Create the two firewalls
This is where most "secure" tutorials quietly betray you. By default, civo firewall create ships the firewall with default open rules. We don't want that. The --no-default-rules flag gives you a clean slate so the only rules on each firewall are the ones you add deliberately.
Jumpbox firewall — the only thing exposed to the internet:
civo firewall create jumpbox-fw --network secure-net --no-default-rules
Allow SSH inbound, and only from your own IP. Find yours with curl -s ifconfig.me, then:
civo firewall rule create jumpbox-fw \--protocol TCP --startport 22 --endport 22 \--cidr 203.0.113.10/32 \--direction ingress --action allow \--label "SSH from my workstation"
Replace 203.0.113.10/32 with your address. This single /32 rule is the difference between a bastion and a billboard.
Backend firewall — no public door at all, SSH reachable only from inside the private network:
civo firewall create backend-fw --network secure-net --no-default-rulescivo firewall rule create backend-fw \--protocol TCP --startport 22 --endport 22 \--cidr 192.168.1.0/24 \--direction ingress --action allow \--label "SSH from private network only"
Since the backends won't have a public IP, this rule is really belt-and-suspenders, but it's the kind you want. The only host that lives on 192.168.1.0/24 and also faces the internet is the jumpbox, so in practice SSH to a backend can only come from the jumpbox.
Step 3: Launch the backend instances
The headline flag is --publicip=none. The CLI defaults this to create, which is exactly the public-by-default behavior we're getting away from.
civo instance create backend-01 \--size g3.small \--diskimage ubuntu-noble \--publicip none \--network secure-net \--firewall backend-fw \--sshkey my-key \--initialuser civo \--waitcivo instance create backend-02 \--size g3.small \--diskimage ubuntu-noble \--publicip none \--network secure-net \--firewall backend-fw \--sshkey my-key \--initialuser civo \--wait
(Use civo size ls and civo diskimage ls to pick a size and image that suit you.)
These instances now have no public IP. From the outside, they don't exist. From the inside, log into one later and run apt update — it just works, because Civo provisions outbound access for private instances automatically. No NAT gateway, no route tables, no extra cost.
Grab their private IPs:
civo instance show backend-01 -o custom -f "Hostname,PrivateIP"civo instance show backend-02 -o custom -f "Hostname,PrivateIP"
Say they come back as 192.168.1.3 and 192.168.1.4.
Step 4: Launch the jumpbox
Same network, but with a public IP and the locked-down jumpbox firewall:
civo instance create jumpbox \--size g3.small \--diskimage ubuntu-noble \--publicip create \--network secure-net \--firewall jumpbox-fw \--sshkey my-key \--initialuser civo \--wait
Get its public IP:
civo instance show jumpbox -o custom -f "Hostname,PublicIP,PrivateIP"
Say it's 74.220.20.5 (public) and 192.168.1.2 (private).
Step 5: Connect through the jumpbox with ProxyJump
The right way to hop through a bastion is SSH's ProxyJump. Your private key stays on your laptop and is never copied to the jumpbox — the jumpbox just forwards the encrypted connection. (Resist the temptation to scp your private key onto the bastion "to make it easier." A compromised bastion with your key on it owns your whole fleet.)
Add this to ~/.ssh/config:
Host civo-jumpHostName 74.220.20.5User civoHost backend-01HostName 192.168.1.3User civoProxyJump civo-jumpHost backend-02HostName 192.168.1.4User civoProxyJump civo-jump
Now reach a private backend directly:
ssh backend-01
SSH transparently lands on the jumpbox first, then tunnels to 192.168.1.3 over the private network. One command, no key on the bastion, and backend-01 was never reachable from the internet to begin with.
A one-off without editing your config looks like this:
ssh -J civo@74.220.20.5 civo@192.168.1.3
Verify the boundary actually holds
The point of all this is what doesn't work. Confirm it:
# Backend instances have no public IPs.# Attempting to SSH directly should fail because there is no public IP.ssh civo@<backend-public-ip># SSH to the jumpbox from an unauthorized IP should be blocked by the firewall.# Backend instances should be reachable only through the jumpbox.ssh backend-01 'hostname && curl -s ifconfig.me'
That last command is worth pausing on: it pulls outbound internet from a box with no public IP. On most clouds you'd have had to stand up a NAT gateway first.
Tightening from here (optional)
This is a deliberately minimal build. A few directions to harden it when you're ready:
- Keep the jumpbox firewall rule scoped to a `/32`. If your IP is dynamic, scope it to your office/VPN CIDR rather than
0.0.0.0/0. - For team access, you don't have to hand out the jumpbox to everyone manually — front it with an SSO/identity-aware access layer (Teleport, Tailscale SSH, Cloudflare Access, and friends) so logins are tied to your identity provider and audited centrally.
- Disable password auth and root login on the jumpbox.
Tear it down
civo instance rm jumpbox backend-01 backend-02 --yescivo firewall rm jumpbox-fw backend-fw --yescivo network rm secure-net --yes
FAQs
Summary
A public IP for every compute instance may be convenient during development, but it isn't the best approach for production environments. By combining a private network, two firewalls, and a single jumpbox, you can keep your Civo Compute instances hidden from the public internet while still allowing outbound internet access for updates and package installations—without deploying a NAT Gateway. This approach reduces your attack surface, centralizes SSH access, and provides a simpler, more secure way to manage your infrastructure on Civo.
Read more
- How Civo Kubernetes Routes Pod Traffic (Single Egress IP Explained)
https://www.kubeblogs.com/how-civo-kubernetes-routes-pod-traffic-single-egress-ip-explained/ - Connect AWS and Civo Privately Using StrongSwan VPN
https://www.kubeblogs.com/cross-cloud-integration-made-easy-building-an-aws-civo-strongswan-vpn-for-private-secure-connectivity/ - Secure Your Kubernetes API with Cloudflare Zero Trust (WARP)
https://www.kubeblogs.com/private-kubernetes-api-cloudflare-zero-trust-warp/

Founder at KubeNine
Vikas Yadav is the founder of Kubenine and a DevOps engineer focused on building scalable cloud infrastructure and developer platforms. His work centers on automation, platform engineering, and modern DevOps practices using technologies such as AWS, Kubernetes, Terraform, and CI/CD pipelines.
With more than seven years of experience in cloud engineering, including previous work at LinkedIn, Vikas helps startups and enterprise teams improve deployment workflows, automate infrastructure, and optimize cloud environments.
Share this article
Further Reading
9 October 2024
Automating infrastructure as code: Deploying Kubernetes with Digger and GitHub actions
21 January 2025
How to mitigate Kubernetes runtime security threats
5 March 2026