PrivateLink traffic always arrives from the load balancer, whatever you configure
Client IP preservation has no effect on inbound PrivateLink traffic. If you turn it on and write client-IP security group rules, direct clients work and every PrivateLink consumer breaks — which is a difficult failure to see from the provider side.
· Venerable Networks
If you expose a service through PrivateLink, the Network Load Balancer in front of it is the boundary consumers reach. There is a detail about what your targets see that is easy to miss and expensive to get wrong.
From the target group attributes documentation:
Client IP preservation has no effect on inbound traffic from AWS PrivateLink. The source IP address of the AWS PrivateLink traffic is always the private IP address of the Network Load Balancer.
Always. Not "unless you enable preservation". The attribute simply does not apply to that traffic.
The failure it produces
You enable client IP preservation because you want real client addresses for logging or for authorisation. Then you update your target security groups to permit those client addresses, which is the correct and necessary follow-up.
Direct clients in the same VPC now work. Every PrivateLink consumer breaks, because their traffic arrives from the load balancer's private address and your security group no longer permits it.
The reason this is nasty is the asymmetry of visibility. You are the provider. Your own testing goes through the direct path, which works. The failure lands entirely on consumers in other accounts, who see a timeout and no explanation, and who cannot inspect anything on your side. The first signal is usually a support ticket from a customer, hours later.
The related restriction
The same page has a second constraint that catches a different design:
Client IP preservation is not supported when a target group contains AWS PrivateLink network interfaces, or the network interface of another Network Load Balancer. This causes a loss of communication to those targets.
So chaining load balancers, or registering PrivateLink ENIs as targets, is incompatible with preservation — and the documented consequence is loss of communication rather than a graceful fallback.
Getting the security group rules right
The rules depend on two independent things: whether preservation is on, and whether the load balancer has its own security group. The target registration documentation splits its guidance accordingly, and the second variable is the one people do not realise is in play.
If the load balancer has an associated security group, reference that security group in the target's rules. This is the cleanest option and the reason to give your NLB a security group in the first place. There is a further subtlety: whether PrivateLink traffic is evaluated against the load balancer's inbound rules is itself configurable, and it changes whether you permit the load balancer's security group or its private IP addresses for that traffic.
If it does not, then:
- Client traffic with preservation on: permit the approved client IP ranges.
- Client traffic with preservation off: permit the load balancer's private IP addresses.
- PrivateLink traffic: always permit the load balancer's private IP addresses.
- Health checks: always permit the load balancer's private IP addresses.
Note that the last two do not vary. If you serve both direct clients and PrivateLink consumers with preservation enabled, you need both kinds of rule, and dropping the load balancer rule when you add the client rule is exactly how the failure above happens.
Getting the client address a different way
If the reason for enabling preservation was visibility rather than authorisation, proxy protocol v2 gives you the address in a header instead of in the packet's source field, which leaves the security group model alone.
It has its own cost: the header is prepended to health check connections as well, where it carries no client information and breaks any target that does not parse it. So it needs application support deployed first.
For PrivateLink specifically, proxy protocol v2 can also carry the VPC endpoint ID as a type-length-value field, which is often what you actually wanted — identifying which consumer rather than which host. That is not available through preservation at all, because the source address has been rewritten by then.
The other places preservation quietly does not apply
Worth having the full list, because each one produces the same "some clients work and some do not" symptom:
- Through a Transit Gateway. Not supported. The target must be in the same VPC or a peered VPC in the same Region.
- Through a Gateway Load Balancer endpoint used to inspect traffic between the NLB and the target, even inside one VPC.
- On IPv6-to-IPv4 translated traffic. The source is always the load balancer's private address. Preservation only works when client and target are both IPv4 or both IPv6.
- On a list of older instance types — C1, CC1, CC2, CG1, CG2, CR1, G1, G2, HI1, HS1, M1, M2, M3, T1. AWS recommends registering those by IP address with preservation disabled.
- NAT loopback, where a target behind an internal NLB connects to that same NLB, is not supported with preservation enabled. The connection can be routed back to the originating target.
The pattern across all of them: preservation requires a direct path between the load balancer and the target. Anything that rewrites or re-encapsulates in between removes it, and the failure is silent because the traffic still arrives — just from a different address than your rules expect.
The check before you enable it
Enumerate who reaches the service, not just how it is configured:
aws ec2 describe-vpc-endpoint-connections \
--filters "Name=service-id,Values=$SERVICE_ID" \
--query 'VpcEndpointConnections[].[VpcEndpointId,VpcEndpointOwner,VpcEndpointState]' \
--output tableIf that returns anything, you have PrivateLink consumers whose traffic will not carry client addresses, and your security group needs a rule for the load balancer regardless of what you do with preservation.
Two labs cover the surrounding ground. The load balancer that hid its clients is preservation versus security group rules, including a place where two AWS pages state opposite defaults for the attribute. The endpoint that was blocked by a security group nobody chose is the consumer side of PrivateLink, where the endpoint works and its security group does not.