↓ Skip to main content

AWS: Services Worth Knowing

· loading
Table of Contents

Only the AWS services that are critical or genuinely worth learning. Each section follows the same shape:

  1. The problem it exists to solve.
  2. The idea, the one design decision everything else follows from.
  3. How it works, enough to reason about it.
  4. Traps, and how to debug them.

Pricing, quotas and every flag are left to the AWS docs, which are linked at the end of each section.


PrivateLink#

The problem
#

Your app runs in a private subnet. A service you need, such as a managed database, another team’s API or a SaaS product, runs in someone else’s VPC, often in another account. How do you connect the two without the internet?

The two obvious answers both connect networks:

  • Internet + NAT. The service must be public, and your NAT IPs become your identity on an allow-list.
  • VPC peering / Transit Gateway. These need non-overlapping CIDRs, and everyone picks 10.0.0.0/16. Once routes exist, every host can reach every host both ways. You wanted one service on one port.
 your VPC 10.0.0.0/16  ◄──── peering? ────►  their VPC 10.0.0.0/16
                          ✗ same CIDR: "route 10.0.0.0/16 → peer" is meaningless

The idea: a door, not a bridge
#

Don’t join the networks. Put a private IP address for that one service inside your own subnet.

flowchart LR
    subgraph C["Your VPC 10.0.0.0/16"]
        APP["app<br/>10.0.12.7"] -->|"TCP 10.0.12.200:443"| ENI["endpoint ENI<br/>10.0.12.200"]
    end
    subgraph P["Provider VPC 10.0.0.0/16"]
        NLB["Network Load Balancer"] --> SVC["service"]
    end
    ENI ==>|"PrivateLink<br/>(AWS network only)"| NLB
  • Provider side: a Network Load Balancer in front of the service, published as an endpoint service (com.amazonaws.vpce.<region>.vpce-svc-…).
  • Your side: an interface endpoint, which is one network interface (ENI) per chosen AZ, each with a private IP from your subnet.

Everything follows from that:

PropertyWhy
Overlapping CIDRs are fineNo routes are exchanged. The ENI is just a local address
One service, not a networkThe door opens onto one NLB, on the ports it listens on
One-wayYou connect in. The provider can’t connect back through it
Provider never sees your IPsIt sees its NLB’s private IP. If it needs to know who you are, it identifies you by endpoint ID (Proxy Protocol v2)

How a request flows
#

sequenceDiagram
    participant A as app 10.0.12.7
    participant D as VPC DNS resolver
    participant E as endpoint ENI 10.0.12.200
    participant N as provider NLB
    A->>D: resolve db.saas.example
    D-->>A: 10.0.12.200 (+ the ENI in the other AZ)
    A->>E: TCP :443 (ENI's security group must allow it)
    E->>N: carried over PrivateLink
    N-->>A: reply (stateful, no return rule needed)

Two steps can fail here: DNS (does the name resolve to the ENIs?) and permission (will both sides let the connection through?).

Who controls what
#

Both sides have to say yes:

Provider controlsYou control
Which AWS accounts may create endpoints (allowed principals)Which subnets/AZs get an ENI
Auto-accept or manual approval, and can reject laterSecurity group on the ENIs: which of your hosts, which ports
Often an app-level allow-list: you register your endpoint ID in their consoleDNS: which names resolve to the ENIs

The SaaS allow-list is the step people forget. AWS shows the endpoint as Available, and the provider still resets every connection until the ID is registered. For third-party services the endpoint policy allows everything, so the security group is your control. Admit only the subnets that need it, never “the whole VPC” if some subnets run untrusted code.

DNS: where it usually breaks
#

The endpoint gives you IPs. Your app uses a hostname. There are two ways to connect them:

Private DNS onPrivate DNS off + your own zone
HowProvider publishes a verified name. AWS adds a hidden private zone to your VPCYou create a Route 53 private zone named after the exact hostname, with an alias to the endpoint’s regional DNS name
EffortZeroOne zone + one record
CatchIf the name is a wildcard (*.db.saas.example), this one endpoint answers every name under it in your VPC. Only one endpoint per VPC can own a private DNS name: a second fails with “conflicting DNS domain”None, as long as the zone is the exact name, not the parent domain

Two resolver rules explain most surprises:

  1. The most specific zone wins. An exact-name zone tenant-a.db.saas.example beats a wildcard zone db.saas.example, but only for that one name. So you can add a dedicated endpoint for one name even when another endpoint’s private DNS already covers the domain.
  2. No fallback. Once the resolver picks a zone, a missing record is NXDOMAIN. It doesn’t try the parent zone or public DNS. That’s why you never create a zone for the parent domain just to add one record: you’d black out every other name under it.

The friendly-name trap
#

You’d like db.internal.mycompany instead of the provider’s ugly name, so you add a CNAME. DNS resolves, and the connection is reset.

A multi-tenant provider serves everyone from the same IPs and picks your tenant from the TLS SNI, which is the name your client was given. A CNAME changes where DNS points, not what the client sends:

Same IP, name in the TLS handshakeResult
provider’s real hostname200 OK
db.internal.mycompanyconnection reset
db.internal.mycompany, with the real name in the HTTP Host headerconnection reset: routing happens before HTTP

The certificate wouldn’t match your name either. Put the real hostname in one config value. If you really need an alias, the only working options are a TLS “server name” override in every client, or a TLS-terminating proxy you run.

Not to be confused with gateway endpoints
#

Gateway endpoints exist only for S3 and DynamoDB. They’re a route-table entry, not ENIs, and they’re free. Add one for S3 in every VPC. Interface endpoints (PrivateLink) cost an hourly charge per AZ plus a per-GB charge, so cost grows with endpoints × AZs.

When it breaks
#

SymptomLikely cause
NXDOMAINNo zone answers the name, or a more specific zone without a record shadows it
Resolves to a public IPPrivate DNS off and no zone of your own, or the client isn’t using the VPC resolver
TimeoutEndpoint security group doesn’t admit this subnet/port, or a NACL blocks it
Connection resetEndpoint ID not registered with the provider, or the SNI isn’t a name they serve
TLS certificate errorConnecting by an alias, IP, or the vpce-… DNS name
Stuck pendingAcceptance / rejectedProvider side: manual approval or rejection
“conflicting DNS domain” on createAnother endpoint already owns that private DNS name, so turn private DNS off on the new one

Endpoint ENIs don’t answer ping. Test with curl, nc, or the real client.

In one breath
#

PrivateLink puts a private IP for one remote service inside your subnets. Routing is trivial and safe by construction: no routes, no CIDR clashes, one-way. All the work is in who may connect (provider’s allow-list + your security group) and which names resolve to that IP (private DNS, or your own exact-name zone), and in using the provider’s own hostname.

Docs: Concepts · Private DNS names · Interface endpoints · NLB: source IP & Proxy Protocol v2 · Route 53 overlapping private zones · “Conflicting DNS domain” · Pricing · Quotas


↑