DigitalOcean Private Droplets: A Bastion Host and Cloud Firewall Checklist
Private Droplets remove the public interface, but production safety depends on the surrounding bastion, VPC, NAT, SSH, and Cloud Firewall design.
DigitalOcean’s new Private Droplet tutorial is useful because it treats “no public IP” as an architecture choice, not a checkbox. A Private Droplet can be a strong boundary for databases, internal APIs, model servers, and other back-end services, but only if the access path is designed deliberately: the Droplet must live in the right VPC, operators need a controlled bastion host, Cloud Firewalls must be scoped on both tiers, and outbound access must be planned through a NAT gateway when the workload needs package or image pulls.
Table Of Content
- Why “private” is different from “public with a firewall”
- The useful guarantee
- The tradeoff to accept before creation
- Reference architecture for safe operator access
- Keep the bastion boring
- Use two firewall policies, not one broad policy
- Build checklist
- 1. Confirm the VPC and egress plan
- 2. Create the Private Droplet intentionally
- 3. Create a minimal bastion host
- 4. Attach a Cloud Firewall to the bastion
- 5. Attach a Cloud Firewall to the Private Droplet
- 6. Connect with OpenSSH ProxyJump
- Verification checklist before production use
- Network reachability tests
- Change-control evidence
- Handoff packet
- Common mistakes to avoid
- Trying to retrofit privacy after launch
- Using the bastion as a shared workstation
- Forgetting outbound dependencies
- Decision rubric
- Sources
The July 1 DigitalOcean Community tutorial, “How to Set Up a Private Droplet and Bastion Host with Cloud Firewalls,” lays out the core pattern: create a Private Droplet, create a bastion host in the same VPC, restrict SSH to the bastion, restrict the private server to accept SSH only from the bastion, and then connect through the jump host. The checklist below turns that pattern into a production review that hosting teams can use before putting a private tier behind real data or internal services.
Why “private” is different from “public with a firewall”
DigitalOcean’s Private Droplets documentation defines Private Droplets as Droplets with VPC-only networking and no direct public connectivity. A traditional Droplet has public and private network paths. A Private Droplet has a single private IPv4 interface and is designed to work with VPC products such as peering, NAT gateways, and private DNS.
The useful guarantee
The operational value is structural: there is no public interface for SSH, databases, admin panels, metrics endpoints, or model-serving ports to accidentally expose. DigitalOcean’s documentation says Private Droplets have no direct public ingress except from a regional or global load balancer, and outbound internet access goes through a VPC NAT gateway when one is configured as the default route for the VPC.
The tradeoff to accept before creation
This is not a setting to toggle later. DigitalOcean documents that existing Droplets cannot be converted to Private Droplets, Private Droplets cannot be converted to standard Droplets, and Reserved IP or BYOIP addresses cannot be assigned to Private Droplets. If the workload needs a public IP later, the migration path is to snapshot or rebuild into a different Droplet design.
Reference architecture for safe operator access
The safe pattern has three controlled edges. First, an administrator reaches a small bastion host over SSH from a known public source address. Second, the bastion reaches the Private Droplet over the VPC. Third, the Private Droplet reaches the internet only when a NAT gateway or other approved egress path is intentionally configured.
Keep the bastion boring
The bastion should not be an application server, CI runner, database client for everyone, or shared troubleshooting box. Treat it as a narrowly scoped access broker: small instance size, current OS image, SSH keys only, no password authentication, minimal packages, aggressive logging, and a firewall rule that accepts SSH only from the administrative network or VPN egress IPs.
Use two firewall policies, not one broad policy
DigitalOcean Cloud Firewalls are network-based, stateful firewalls for Droplets, and the firewall documentation says traffic is blocked unless a rule expressly permits it. For this design, use one firewall attached to the bastion and a separate firewall attached to the Private Droplet. The bastion policy answers “who can enter the jump host?” The private-tier policy answers “what can the jump host reach?”
Build checklist
1. Confirm the VPC and egress plan
Place the bastion and Private Droplet in the same VPC and region unless you have a documented peering design. If the Private Droplet must download OS updates, container images, artifacts, or packages, create a VPC NAT gateway first or schedule one before cutover. DigitalOcean’s NAT gateway documentation describes NAT gateways as a service that centralizes outbound internet access for VPC resources while keeping back-end IPs isolated.
2. Create the Private Droplet intentionally
For Control Panel builds, DigitalOcean says to clear both the Public IPv4 address and Public IPv6 address options in the Networking section. For API builds, make the private status explicit in the request and keep the JSON in your change record.
# Context: DigitalOcean API v2 private Droplet creation example for Ubuntu 24.04.
# Replace region, size, SSH key fingerprint, and token before use.
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -d '{
"name": "app-private-01",
"region": "sfo3",
"size": "s-1vcpu-1gb",
"image": "ubuntu-24-04-x64",
"ssh_keys": ["de:ad:be:ef:de:ad:be:ef:de:ad:be:ef:de:ad:be:ef"],
"monitoring": true,
"with_droplet_agent": true,
"public_networking": false
}' "https://api.digitalocean.com/v2/droplets"
3. Create a minimal bastion host
The bastion can be a standard Droplet with a public IP because its job is to terminate administrator SSH. Keep it in the same VPC as the Private Droplet, assign only the operators’ SSH keys, and avoid installing workload dependencies. If multiple teams need access, separate identity and logging are usually better than sharing one Unix account.
4. Attach a Cloud Firewall to the bastion
The bastion firewall should allow SSH from a narrow source and should not expose application ports. The following command uses the documented doctl compute firewall create shape from DigitalOcean’s firewall documentation, but the IDs and addresses are placeholders.
# Context: DigitalOcean doctl firewall command for a bastion Droplet.
# Replace <ADMIN_PUBLIC_IP>, <PRIVATE_DROPLET_PRIVATE_IP>, and <BASTION_DROPLET_ID>.
doctl compute firewall create --name "bastion-ssh-only" --inbound-rules "protocol:tcp,ports:22,address:<ADMIN_PUBLIC_IP>/32" --outbound-rules "protocol:tcp,ports:22,address:<PRIVATE_DROPLET_PRIVATE_IP>/32" --droplet-ids "<BASTION_DROPLET_ID>"
5. Attach a Cloud Firewall to the Private Droplet
The private-tier firewall should allow SSH only from the bastion path, and it should allow outbound ports only when the workload requires them. If a NAT gateway is in use for package pulls, allow the required egress ports and log the reason. Do not use 0.0.0.0/0 as an inbound source for a private-tier SSH rule just because the Droplet lacks a public interface.
# Context: DigitalOcean doctl firewall command for a Private Droplet.
# Replace <BASTION_DROPLET_ID> and <PRIVATE_DROPLET_ID>.
doctl compute firewall create --name "private-droplet-from-bastion" --inbound-rules "protocol:tcp,ports:22,droplet_id:<BASTION_DROPLET_ID>" --outbound-rules "protocol:tcp,ports:443,address:0.0.0.0/0" --droplet-ids "<PRIVATE_DROPLET_ID>"
6. Connect with OpenSSH ProxyJump
OpenSSH’s ssh_config manual describes ProxyJump as one or more jump proxies that are visited before a TCP forwarding connection is established to the ultimate target. Keep the operator’s SSH configuration explicit so access reviews can see the bastion boundary.
# Context: OpenSSH client configuration on an operator workstation.
# Replace public DNS/IP, private IP, users, and key paths.
Host do-bastion
HostName bastion.example.net
User ops
IdentityFile ~/.ssh/id_ed25519_sxz_bastion
IdentitiesOnly yes
Host do-private-app-01
HostName 10.10.0.12
User ubuntu
ProxyJump do-bastion
IdentityFile ~/.ssh/id_ed25519_sxz_private
IdentitiesOnly yes
After saving the config, the operator connects with ssh do-private-app-01. If that succeeds while direct public SSH to the private host fails, the access path is behaving as intended.
Verification checklist before production use
Network reachability tests
- From an unapproved public IP, SSH to the bastion should fail.
- From the approved admin IP or VPN egress, SSH to the bastion should succeed.
- Direct SSH to the Private Droplet’s private IP from outside the VPC should fail.
- SSH through
ProxyJumpshould succeed for authorized operators. - If NAT is required, package or artifact pulls should work from the Private Droplet; if NAT is not required, broad outbound internet access should fail.
Change-control evidence
Record the Droplet IDs, VPC ID, NAT gateway ID if present, bastion public IP or DNS name, private Droplet private IP, firewall IDs, allowed source CIDRs, SSH key fingerprints, and owner of each rule. This is the difference between a safe access path and tribal knowledge.
Handoff packet
Before handoff, attach screenshots or API output showing the Private Droplet has no public IPs, the bastion firewall allows SSH only from approved sources, the private firewall allows SSH only from the bastion path, and the NAT gateway decision is documented. Also include a break-glass procedure that does not require widening SSH to the whole internet.
Common mistakes to avoid
Trying to retrofit privacy after launch
If a standard Droplet was created with a public interface, do not describe it as equivalent to a Private Droplet after adding firewall rules. The absence of a public interface is the point of the Private Droplet design, and DigitalOcean documents conversion as unsupported.
Using the bastion as a shared workstation
Every extra package, credential, and long-running process on the bastion increases the blast radius of the one host that can reach the private tier. Keep the bastion short-lived or tightly maintained, rotate keys when operators leave, and forward application access through purpose-built tooling rather than turning the jump host into a general-purpose admin desktop.
Forgetting outbound dependencies
A private server that cannot download updates or pull required images will fail in ways that look like DNS, package repository, or container registry problems. Decide whether the workload needs NAT before production. If it does, treat the NAT gateway and outbound firewall rules as production dependencies with monitoring and cost ownership.
Decision rubric
Use a Private Droplet when the service should not have a public IP at all: databases, internal APIs, worker nodes, model servers, build support services, or regulated back-end systems. Use a standard Droplet with a Cloud Firewall when the service is intentionally public and the main requirement is to limit ports and sources. Use a load balancer in front of private compute when client traffic must enter, but direct host access should remain private.
The safe outcome is simple: humans enter through one audited bastion path, machines reach the internet only through an approved egress design, and the private workload never relies on “we remembered to close the public port” as its security boundary.
Sources
- DigitalOcean Community: How to Set Up a Private Droplet and Bastion Host with Cloud Firewalls
- DigitalOcean Docs: Private Droplets
- DigitalOcean Docs: How to Create a NAT Gateway
- DigitalOcean Docs: How to Create Firewalls
- OpenBSD manual pages: ssh_config ProxyJump
Image credit: IBM Portable Modular Data Center – power and network cable rails into a server rack by Raysonho @ Open Grid Scheduler / Grid Engine, licensed under Creative Commons Attribution 3.0 via Wikimedia Commons. Image cropped to 16:9, resized, and converted to WebP for sxz.io.








No Comment! Be the first one.