The addresses
Both addresses are single hosts, not ranges — a
/32 each. Neither has changed since the date above.
Allow both. Production carries every delivery to a customer destination, for agents in the production and sandbox environments alike: the environment a record came from decides what is copied, not where it is copied from. Staging is Lua’s own pre-production platform. You will normally only ever see the production address; admit staging as well so that a Lua-side test against your endpoint is not silently blocked.
There is no per-customer address and no regional egress. Every organization’s deliveries leave from the same addresses, so seeing one of them in your logs identifies Lua, not your account. Use
X-Lua-Drain-Id — or a signature — to tell your own drains apart on a shared receiver.The machine-readable list
The same list is served as JSON, so an allow-list can be refreshed from CI rather than from this page:egress.json, which you can fetch, diff, and feed straight into a firewall rule.
egress.json
Pull the production addresses in one line:
Output
How the list changes
It changes rarely, and never quietly. An address is published before it is used, never after. A new address appears on this page and inegress.json — with a higher version — while deliveries are still leaving from the current one. Only once it is published does traffic start arriving from it. The reverse order, rotating first and telling you afterwards, is the failure this policy exists to prevent.
A retired address is removed from the list only after deliveries have stopped leaving from it, so an allow-list that still holds an old entry is stale, never broken.
Changes are announced in the changelog as well as here. If you want to be told rather than to poll, watch the version field with the guard above, or write to [email protected] to be added to the notification list for egress changes.
Delivery policy
Every delivery obeys the following, on every attempt, with no per-drain override.The destination must be HTTPS
Onlyhttps:// endpoints are accepted. A plain http:// endpoint is refused when the drain is created and refused again on every send — there is no flag, no environment, and no support exception that relaxes it.
Deliveries go to TCP 443 unless your endpoint URL names another port.
The destination must be a public host
Before each attempt, the endpoint’s hostname is resolved and every A and AAAA record is checked. If any one of them lands in a private, loopback, link-local, unique-local, or cloud metadata range, the delivery is refused. The check runs at send time, every time, and its result is never cached — a name that answered a public address when the drain was created and answers169.254.169.254 an hour later is refused at that moment, not trusted on the strength of the earlier answer.
A refusal is terminal: it is recorded with error class ssrf and is not retried, because the same destination would be refused again. A hostname that fails to resolve at all is a different thing — error class dns, and it does retry.
This is why a receiver on a private network needs something public in front of it: a load balancer, a tunnel, or a relay. Pointing a drain at
10.0.0.5 or at a name that resolves to one will never deliver, however the network is arranged on your side.Redirects are never followed
A3xx response is a terminal failure, recorded with error class redirect. It is not a transient error and it is not retried.
A redirect is how a destination that passed the public-host check hands the request to one that never did, so following it would make the check meaningless. Point the drain at the final URL instead — including the trailing slash, if your receiver redirects to add one.
Each attempt gets ten seconds
One attempt, connection through response, is bounded at 10 seconds. A slower response is abandoned and recorded with error classtimeout, and the batch is retried.
At most 4 requests are in flight to any one drain at a time, so a slow destination occupies four delivery slots and never the whole pool.
Retry and backoff
A retryable failure — a network error, a DNS failure, a timeout, or a retried status code — puts the batch back in the queue with a delay drawn uniformly from zero up to a ceiling that doubles per attempt and stops at 60 seconds.
Because the delay is drawn from the whole interval rather than sitting at its top, a destination coming back up is not hit by every drain’s backlog in the same instant. A
Retry-After header — delta-seconds or an HTTP date — overrides the draw and is honoured exactly, clamped at one hour.
Records stop being retried after 6 hours of waiting. An endpoint that returns from a long outage receives the last six hours, not a six-day flood. Delivery guarantees has the full status-code table and the health states.
Headers on every delivery
Every delivery, to every destination type, identifies itself:Content-Type, Content-Encoding, Content-Length, Host, or any header beginning X-Lua-.
Bodies are always gzipped, so every delivery also carries Content-Encoding: gzip.
Deliveries to a generic HTTPS drain carry the Lua protocol headers as well:
X-Lua-Signature is an HMAC-SHA256 over the uncompressed body, computed before the body is gzipped, with the timestamp t in whole Unix seconds. A second v1= value appears for the overlap window after a secret rotation, so verify against any of them. The signature is only valid within five minutes of t. Verify signatures has a worked verifier.
Allow-list examples
Admit both addresses on TCP 443 inbound to your receiver.- AWS security group
- nftables
- iptables
- nginx
- Cloudflare WAF
If deliveries are not arriving
Next steps
Verify signatures
Prove a batch came from Lua, with a worked verifier.
Delivery guarantees
At-least-once, the status-code table, and the health states.
Generic HTTPS
Your own receiver, signed, with a copy-paste example.
lua drains
Every verb, flag, and
--json shape.
