Description
This rule verifies that Load Balancers are not unintentionally exposed to the public internet. A DigitalOcean Load Balancer's exposure is governed by its network field: EXTERNAL load balancers receive a public IP address and are reachable from the internet, while INTERNAL load balancers have no public IP and are reachable only by resources inside the same VPC network.
The rule inspects each load balancer's network placement and forwarding rules and flags EXTERNAL load balancers that front backend services expected to be internal-only, based on naming, tags, or architecture documentation. It also considers whether a public-facing load balancer that must remain external restricts its inbound sources with a load balancer firewall (allow/deny lists).
Remediation is to recreate an unintentionally public load balancer as INTERNAL, or — where some public exposure is genuinely required — restrict its inbound sources with an allow list, and document the justification for any load balancer that must stay internet-facing.
EXTERNAL (public-IP) load balancers fronting services that naming/tags/architecture indicate should be internal-only.
Public-facing load balancers with no firewall allow/deny list restricting inbound sources.
Internet-facing load balancers with no documented business justification for public exposure.
Rationale
Every internet-facing endpoint expands the attack surface: a public load balancer fronting a service that only internal systems need to reach exposes that service to internet-wide scanning, brute force, and exploitation attempts it would never see on a VPC-internal address. Placing such load balancers on the INTERNAL network removes the public IP entirely, so the service is reachable only from within the VPC.
When public reachability is a real requirement, DigitalOcean Load Balancers support a firewall with allow and deny lists of IPs and CIDRs (configurable via the API or doctl), where deny rules take precedence over allow rules. Restricting inbound sources to known networks limits exposure even for load balancers that must remain external.
Internal-only services on a public load balancer are needlessly exposed to the internet.
INTERNAL placement removes the public IP so only in-VPC resources can connect.
Load balancer firewall allow lists scope inbound access when public exposure is unavoidable.
Impact
Pros:
Removes public exposure for services that only need to be reached internally.
Shrinks the internet-facing attack surface of the account.
Firewall allow lists constrain who can reach load balancers that must stay public.
Cons:
The EXTERNAL/INTERNAL network setting is fixed at creation, so switching requires recreating the load balancer and repointing DNS.
Load balancer firewall allow/deny lists are configurable only via the API or doctl, not the Control Panel.
An overly tight allow list can block legitimate clients if source ranges change.
Default Value
When you create a DigitalOcean Load Balancer, the network type defaults to EXTERNAL, meaning it is assigned a public IP and is internet-facing unless you explicitly create it as INTERNAL.
Pre-Requisite
A DigitalOcean account role that can view and create Load Balancers and view VPCs.
doctl authenticated with a DigitalOcean API token (or $DIGITALOCEAN_TOKEN exported) — required for firewall allow/deny configuration.
An inventory of which backend services are intended to be internal-only, and the source IPs/CIDRs of legitimate clients for any service that must stay public.
Remediation
Test Plan
Using the DigitalOcean Control Panel:
Sign in to cloud.digitalocean.com.
Go to Networking, then the Load Balancers tab.
For each load balancer, check whether it has a public IP address (internet-facing) or is marked internal / VPC-only.
Cross-reference the fronted service against your architecture notes to decide whether public exposure is intended.
Pass: Load balancers fronting internal-only services have no public IP (INTERNAL), and any internet-facing load balancer has a documented reason to be public.
Fail: An internal-only service is fronted by a public (EXTERNAL) load balancer with no business justification.
Using doctl or the API:
List load balancers and inspect the network field (and any firewall allow/deny lists).
curl -s -X GET "https://api.digitalocean.com/v2/load_balancers" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" | jq '.load_balancers[] | {id, name, network, ip, firewall}'
doctl compute load-balancer list --format ID,Name,IP
Pass: load balancers fronting internal-only services report network=INTERNAL (no public ip), and any network=EXTERNAL load balancer is documented and/or scoped by a firewall allow list. Fail: a load balancer that should be internal reports network=EXTERNAL with a public IP and no justifying firewall restriction.
Implementation Steps
Using the DigitalOcean Control Panel:
Decide the correct placement: INTERNAL for services only in-VPC resources need to reach, EXTERNAL for genuinely public services.
In Networking, then Load Balancers, click Create Load Balancer and choose the Internal network type (VPC-only) if the service should not be public. The network type cannot be changed on an existing load balancer, so create a replacement.
Attach the same Droplets/tag and recreate the forwarding rules and health check on the new internal load balancer.
Repoint DNS (or the calling service) from the old public load balancer to the new internal one, then delete the old public load balancer.
For a load balancer that must remain public, restrict inbound sources with a firewall allow list (configured via doctl/API — see below) and document the justification.
Using doctl or the API:
The network type (EXTERNAL/INTERNAL) is set only at creation, so create a new load balancer with the desired network; firewall allow/deny lists are set via doctl or the API.
doctl compute load-balancer create --name internal-lb --region nyc3 --network INTERNAL --vpc-uuid <vpc-uuid> --tag-name web --forwarding-rules "entry_protocol:https,entry_port:443,target_protocol:http,target_port:80,certificate_id:<certificate-id>"
doctl compute load-balancer update <load-balancer-id> --allow-list "cidr:203.0.113.0/24,ip:198.51.100.7"
curl -s -X PUT "https://api.digitalocean.com/v2/load_balancers/<load-balancer-id>" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H "Content-Type: application/json" -d '{"firewall": {"allow": ["cidr:203.0.113.0/24", "ip:198.51.100.7"], "deny": []}}'
Backout Plan:
Keep the original load balancer running until the internal replacement is verified and DNS has fully cut over.
If internal-only placement breaks legitimate external access, revert DNS to the original public load balancer.
If a firewall allow list blocks legitimate clients, widen or clear it with doctl compute load-balancer update (remember deny rules take precedence over allow rules).
Re-test connectivity from both intended in-VPC and external clients after any change.