Tenant routing
0-day streakReview

Your progress

Level 1

0 XP100 XP to level 2
0day streak
0best streak
0topics started
0cards reviewed
    Review cards

    Progress is stored only in this browser. No account is required.

    Layer 4 · Full breakdown · 20 min · 5 figures

    Routing requests to tenants

    How a multi-tenant SaaS works out which tenant a request is for and where that tenant lives: subdomains, custom domains with ACME certificates, tokens and API keys, a cached tenant catalog, cell routers, tenant moves and the routing attacks to defend against.

    Routing requests to tenants is the work a multi-tenant SaaS does at the front door of every request: decide which tenant the request is for, then find where that tenant lives and send it there. The first half is identification, from a hostname, a path, a token or a key. The second is placement, a lookup in a tenant catalog that names the region, stamp and shard holding the tenant’s data. Get either wrong and a request lands in the wrong place, or in the wrong tenant.

    Picture it first: the courier desk at a tech park

    A tech park in Gurugram has 300 companies spread over 12 towers and one courier desk at the gate. Every parcel raises two questions.

    Who is it for? The label names a company. That’s enough to sort a parcel, but a label is just ink: anyone can write “Acme” on a box. Anything valuable, such as a cheque book or a laptop, is handed over only against a signed authority letter or a staff badge.

    Where is that company now? The desk keeps a register: Acme, Tower 4, floor 7. Clerks don’t walk to the facilities office for each parcel. They keep a photocopy at the counter, and the office phones the desk whenever a company moves.

    When Acme moves to Tower 9, the register changes once. A trolley already on its way still reaches Tower 4, and Tower 4’s reception neither signs for the parcel nor bins it. It sends it back: “moved, check the register”.

    Map it back. The label is the hostname or path: a hint. The authority letter is a signed token, API key or client certificate: proof. The register is the tenant catalog, the photocopy is the router’s in-memory copy, the phone call is a pushed update, and Tower 4’s reception is the old stamp answering a stale route with “wrong cell”.

    Two questions, one request path

    In a SaaS split into deployment stamps and shards, a request crosses several hops, and each needs only part of the answer.

    1. DNS resolves acme.app.example to your edge, usually through one wildcard record, so DNS knows nothing about tenants.
    2. The edge (a CDN, global load balancer or front door) terminates TLS. The client named the host in the handshake, so the edge can present the right certificate before it sees any HTTP.
    3. The tenant resolver turns the hostname or token into a tenant ID, and the tenant ID into a placement: region, stamp, shard, tier and status.
    4. The stamp router in the tenant’s home region hands the request to the right stamp, and refuses tenants it doesn’t own.
    5. The app sets tenant context from the verified token, as what is a tenant? describes.
    6. The data layer connects to the tenant’s shard, a catalog lookup of its own covered in multi-tenant data.
    One request for tenant acme. DNS and the edge know nothing about tenants; the resolver maps the host to a tenant and the tenant to a stamp, using a copy of the catalog pushed by the control plane.Notice The request never names its stamp. Placement comes only from the catalog, so moving a tenant changes one record, not any client.

    Keep the two questions apart. Identification may read any signal in the request, but only signed ones count for authorisation. Placement is a lookup in data owned by the control plane, and the request never gets a say in it. Azure’s guidance on mapping requests to tenants draws the same line: work out the tenant context, forward to the deployment that hosts the tenant, and still authorise the request there, because a hostname that names a tenant grants nothing.

    Where the tenant hides in a request

    Azure’s mapping guide lists the places a request can name its tenant: the domain name, the URL path, a query string, a header, a token claim, an API key and a client certificate. Some sit in the TLS handshake, before any HTTP exists; the rest are in the HTTP request.

    Where each identification signal sits in an HTTPS request. No real request carries all seven; a browser request typically has the host and a session token, an integration an API key or client certificate.Notice The hints (host, path, a client-sent header) are all things the client simply types. Only the three signals in green prove anything.

    Hostname: subdomain or custom domain

    acme.app.example or support.acme.com arrives twice: as the server name in the TLS handshake and as the Host header. Every browser sends it with no login, so it is the natural signal for choosing the login page, the branding and the route. But the user typed it, so it is a hint. The next section covers the DNS, certificate and cookie details.

    Path prefix

    app.example/t/acme/invoices needs no DNS or certificate work and is easy in local development. But every tenant shares one origin, so the browser does nothing to separate them, and every link the app builds must carry the prefix. A query string (?tenant=acme) is worse: it falls off links and leaks into logs.

    X-Tenant-Id: t_7f3a is fine between your own components and never from a client. Shopify’s pods architecture shows the trusted version: software in its load balancers, called Sorting Hat, matches each request to a pod and adds a header that application servers use to pick the right datastore. Your edge strips any copy the client sent, then sets its own. Azure adds that custom headers don’t work for ordinary browser navigation, so from the outside they suit APIs and clients you control.

    Token claim

    For API calls, the tenant claim in a signed access token is authoritative: it was issued after login for one tenant, and editing it breaks the signature. Routing on it means the gateway reads the token, so verify it there too; with signing keys cached, that costs no network call. See tokens and JWTs.

    API key

    Server-to-server integrations often send a key. Azure describes two designs: a random key looked up in a table that records its tenant, or a key that embeds the tenant ID and is signed with an HMAC so the gateway can check it without a lookup. Keys are long-lived and carry no claims, so store them hashed, scope them narrowly and plan rotation.

    Client certificate

    Banks, large B2B customers and unattended devices may authenticate with mutual TLS. A table maps the certificate’s subject or fingerprint to a tenant. It is strong proof, but you now run issuance, renewal and revocation for every customer; see service-to-service auth and mTLS.

    Combine them. A browser request to acme.app.example uses the host to choose Acme’s login page and route, then the session token proves the user belongs to Acme, and a mismatch gets a 403, as what is a tenant? explains. Routing on a hint is fine when the destination checks again. Authorising on one never is.

    Method User experience Setup cost TLS work Browser isolation between tenants Fits APIs? Spoofable? Rename cost
    Subdomain acme.app.example Branded, memorable One wildcard DNS record One wildcard certificate, one label deep Own origin, but one site: parent-domain cookies leak Yes Yes: a hint Redirect the old name; users sign in again
    Custom domain support.acme.com Fully branded Customer CNAME plus ownership check A certificate per domain, chosen by SNI Own site: the strongest Rarely used Yes, a hint; ownership is verified once The customer’s project, plus a new certificate
    Path /t/acme/ Plain None None None: one origin for all Yes Yes: a hint URL change and redirects
    Header X-Tenant-Id Invisible None None Not applicable Internal hops only Yes, unless your edge sets it None
    Token claim Invisible Identity provider adds the claim None Not applicable Yes, the default No: signed None: it carries the immutable ID
    API key Invisible Key table and rotation None Not applicable Server to server No, if random and secret None
    Client certificate Invisible Certificate issuance per customer Client certificates and trust stores Not applicable B2B integrations, devices No None
    Identification signals compared. Most products use a host for the browser, a token claim for authorisation, and API keys or client certificates for integrations.

    Subdomains and custom domains

    Most B2B products give every tenant a subdomain and sell custom domains on a higher tier. Users barely notice the difference; your operations team will.

    Subdomains: one record, one certificate

    A subdomain costs nothing per tenant. One wildcard DNS record, *.app.example, sends every tenant name to your edge, and one wildcard certificate covers their TLS, so signing up kirana.app.example is just a catalog row. Azure’s domain-name guidance adds regional stems, such as *.eu.app.example, when tenants live in different regions.

    The single certificate matters. Let’s Encrypt allows 50 new certificates per registered domain every 7 days, and every tenant name counts against app.example: a week of 400 sign-ups with a certificate each would need 400 ÷ 50 = 8 weeks of quota. A wildcard is one certificate, issued only through the DNS-01 challenge.

    Reserve www, api, admin, login and your brand names before the first sign-up, and never give a freed slug to a new tenant (what is a tenant? explains why).

    A DNS wildcard is wider than a certificate wildcard

    In DNS, *.app.example can match several labels, so eu.acme.app.example resolves. A certificate for *.app.example matches exactly one label: acme.app.example, not eu.acme.app.example and not app.example. Add a deeper level and browsers reject a host that resolves fine, so every stem needs its own wildcard certificate.

    Custom domains: prove, point, certify

    A custom domain such as support.acme.com lives in the customer’s DNS, which you can’t edit. Onboarding needs two separate proofs: that Acme owns the name, and that a certificate authority will issue for it.

    Onboarding Acme’s custom domain support.acme.com. The numbers match the steps listed below the figure.Notice Ownership (the TXT record) and the certificate (the ACME challenge) are proved separately, and the edge only ever picks a certificate by the name the browser sends.
    1. You give Acme’s admin a random value, new for every attempt. Acme publishes it as a TXT record and points support.acme.com at acme.app.example with a CNAME.
    2. Your workflow checks the TXT record (ownership), the CNAME (traffic will arrive) and any CAA record, which limits the CAs allowed to issue: if Acme’s names only another CA, your order fails until Acme adds yours.
    3. It orders a certificate over ACME.
    4. The CA checks control of the name. HTTP-01, the usual choice, fetches a token from http://support.acme.com/.well-known/acme-challenge/… on port 80, and the CNAME brings that request to your edge; Let’s Encrypt notes it works for domains CNAMEd to a hosting provider. DNS-01 needs a TXT record at _acme-challenge.support.acme.com, which Acme must delegate to your zone with another CNAME, but it can issue before any traffic moves and is the only route to a wildcard. TLS-ALPN-01 checks inside a TLS handshake on port 443, for proxies that can’t use port 80.
    5. The certificate and key go into a store.
    6. The store pushes them to every edge node.
    7. The browser names support.acme.com in SNI, the edge presents Acme’s certificate, and the host map returns t_7f3a.

    Rate limits shape bulk onboarding. Let’s Encrypt allows 300 new orders per account every 3 hours, or 300 × 8 = 2,400 a day, so a customer bringing 10,000 storefront domains needs 10,000 ÷ 2,400 ≈ 4.2 days unless you request a higher limit. Five failed validations of one name in an hour block it for that account, so check DNS before ordering. Renewals through ACME Renewal Info (ARI) are exempt from every limit.

    When a tenant leaves: dangling DNS

    In Azure’s example, Fabrikam leaves but forgets its CNAME; an attacker signs up as a new tenant, claims invoices.fabrikam.com, passes a CNAME-only check because the record still points at the platform, and serves a convincing login page to Fabrikam’s staff. Defend with a fresh random TXT value on every attempt, release a domain only after the customer has deleted the CNAME, never reuse tenant slugs, and delete your own per-tenant records when a tenant leaves.

    Cookies: subdomains share a site

    acme.app.example and globex.app.example are different origins, but cookies are looser than origins. A cookie set with Domain=app.example goes to every subdomain, so Acme’s session travels to globex.app.example, where a script Globex publishes, such as a custom theme, can read it unless it is HttpOnly. In reverse, any subdomain can set a cookie for app.example that then lands on Acme’s users.

    The fixes, cheapest first. Host-only cookies: omit Domain, and the cookie goes only to the host that set it. The __Host- prefix: browsers accept __Host-session only if it is Secure, has Path=/ and no Domain, so a sibling can’t set or overwrite it. A separate domain for tenant content: if tenants publish their own HTML or script, serve it from another registered domain and consider listing that as a public suffix. Browsers refuse cookies scoped to a public suffix, as MDN’s github.io example shows, so each tenant becomes its own site. Listing takes a long time to reach every browser, so treat it as permanent.

    The tenant routing table

    A router needs only a slice of the tenant record in what is a tenant?, with nothing personal in it, so the map can be copied to every region even when tenant data must stay in one.

    { "tenant_id": "t_7f3a", "hosts": ["acme.app.example", "support.acme.com"],
      "region": "ap-south-1", "stamp": "stamp-07", "shard": "pg-03",
      "tier": "enterprise", "status": "active", "version": 43 }

    The router builds two indexes: host to tenant, for browsers, and tenant to placement, for tokens and keys. status acts before any stamp sees the request: active routes, moving routes to the old stamp until the flip, suspended gets a page from the edge, and deleted looks exactly like an unknown host.

    Where the lookup runs

    At the edge, the lookup rejects unknown hosts and picks the region before a request crosses an ocean, but the map must sit at every point of presence. A regional gateway holds only its region’s tenants. Many products split the work: the edge maps host to tenant to region, and a regional router maps tenant to stamp.

    Push the whole map

    Size decides the design. At 200,000 tenants and about 300 bytes an entry, indexes included, the map is 200,000 × 300 B = 60 MB: small enough for every router’s memory, so a lookup is a hash-map read with no network call.

    The alternatives are worse. A remote lookup per request turns a sale-day peak of 80,000 requests a second into 80,000 catalog reads a second. A per-entry cache with a 60-second TTL still leaks: 40 router nodes × 20,000 tenants active a minute ÷ 60 s ≈ 13,000 catalog reads a second, and a moved tenant can reach its old stamp for a full minute.

    A pushed map costs no reads per request. In AWS’s cell-router example the control plane writes the mapping to S3 and each router keeps it in memory, with a listener applying changes. Most teams use that hybrid: a full snapshot at start-up, then numbered deltas, where a tenant move changes one 300-byte entry (control plane vs data plane covers push and pull).

    Unknown hosts. With a wildcard record every name resolves, so scanners and typos reach you, and if each miss reads the catalog, anyone can flood it by inventing names. With the whole map in memory, a miss is a local 404. If you cache only entries you have seen, cache misses too, for 30 to 60 seconds, and push a new tenant’s entry before sign-up shows its URL.

    When the catalog is down, routers serve the last good copy: no expiry on the read path, a copy on local disk for restarts, and an alert on the copy’s age. That is the static stability of control plane vs data plane: onboarding and moves wait, existing tenants keep working. A router with no copy should refuse traffic, since an empty map returns 404 for everyone.

    Two maps in two places. The edge holds the global host-to-tenant-to-region map; the regional router holds tenant-to-stamp for its own region. The control plane pushes snapshots and deltas to both.Notice No request waits on the catalog: known and unknown hosts are both answered from memory, and the push arrows only carry changes.

    DNS routing versus a gateway lookup

    There are two ways to reach a stamp. AWS lists DNS as a cell router: each tenant gets its own record, such as acme.app.example CNAME stamp-07.ap-south-1.app.example. Route 53’s data plane carries a 100% availability SLA, and there is no extra hop or router fleet. The alternative is the design so far: one wildcard record into a routing layer that looks the tenant up.

    DNS routing pays at move time. As Azure notes, a DNS change reaches clients only as cached answers expire: with a 300-second TTL, resolvers keep sending Acme to stamp-07 for up to 5 minutes after the flip, and some clients hold answers longer, so the old stamp must redirect for hours. DNS also sees only the hostname, and you manage 200,000 records.

    Per-tenant DNS record Wildcard DNS + gateway lookup
    Extra hop None One proxy hop
    Shared failure domain Your DNS provider’s data plane The router fleet: build it as cells
    Records to manage One per tenant One per stem
    Moving a tenant Waits for TTLs and resolvers that ignore them A pushed map change: seconds
    Routes on Hostname only Host, path, token, API key, client certificate
    Fits best Few large tenants, dedicated stamps, non-HTTP traffic Many tenants, frequent moves, APIs
    Per-tenant DNS records against one wildcard record and a gateway lookup.

    You can mix them: regional stems in DNS pick the region, and a gateway inside the region picks the stamp.

    Keep the routers thin

    The router is the one component every cell shares, so AWS’s guidance keeps it simple: minimal business logic, a cheap mapping, cells hidden from clients, and other cells still served when one is unreachable. Because it holds every cell’s mapping, build the router itself as cells: independent fleets, each with the full map, deployed one at a time.

    • In the router: parse the host or token, read the map, forward; at most a coarse per-tenant rate limit.
    • Not in the router: entitlement checks, catalog calls, experiments, anything needing a database.
    • Bulkheads per stamp: separate connection pools and timeouts, so a slow stamp-07 can’t use up the capacity serving stamp-12.

    Moving a tenant

    Tenancy models covers the data side of a move. Here is the routing side, for Acme moving from stamp-07 to stamp-12, following AWS’s four phases for cells: clone, flip, redirect, forget.

    1. Mark it moving. The entry becomes status: moving with target: stamp-12. Routers still send Acme to stamp-07 while stamp-12 fills with a copy that isn’t authoritative.
    2. Freeze writes. Once the copy is seconds behind, stamp-07 answers Acme’s writes with 503 and Retry-After: 2 while in-flight writes finish and the last changes reach stamp-12. With a 2-second cap on in-flight requests and half a second of replication lag, writes pause for about 2.5 seconds.
    3. Flip. stamp-07 records that it no longer owns t_7f3a and where it went; stamp-12 becomes authoritative; the catalog moves to v43. Writes resume on stamp-12 at once.
    4. Push. Routers pick up v43 within seconds; until then, some still send Acme to stamp-07.
    5. Bounce stale requests. stamp-07 neither serves them nor answers 404. It returns 421 Misdirected Request, which HTTP defines for a server that can’t give an authoritative answer for the request, plus the new location. The router updates its entry and retries once on stamp-12. RFC 9110 allows retrying a 421 on another connection even for non-idempotent methods, since nothing was processed, so a bounced POST can’t create an invoice twice.
    6. Reconnect long-lived connections. WebSockets and event streams opened before the flip are pinned to stamp-07 and never pass the router again, so stamp-07 closes them and clients reconnect, with jitter, to stamp-12 (real-time connections).
    7. Forget. After a grace period longer than any router copy or DNS answer can be stale, say 24 hours, stamp-07 deletes Acme’s data, keeping the small tombstone that answers 421 for longer.
    The routing side of moving Acme from stamp-07 to stamp-12. Steps 1 to 3 on the arrows: a router still on v42 sends a request to stamp-07, stamp-07 answers 421 with the new location, and the router retries once on stamp-12.Notice Writes pause only between freeze and flip. Routers on the old map cost one extra hop, not an error.

    In the interview

    Say it plainly: “The old stamp never serves a tenant it no longer owns and never says 404. It says ‘not here’, the router retries once, and the user sees one slower request.”

    With DNS routing, step 4 lasts as long as the TTL, so lower it a day before the move. Azure also suggests a self-refreshing maintenance page if endpoints change mid-migration.

    Routing attacks to defend against

    Everything a router reads arrives from the client, so every signal is also an attack surface.

    Attack What goes wrong Defence
    Host-header injection A forged Host or X-Forwarded-Host lands in a link, redirect or cache entry Accept only catalog hosts; build links from the tenant’s canonical host; strip override headers
    Open redirect A return_to after login sends users, and sometimes tokens, anywhere Allow relative paths or the same tenant’s hosts only
    Cache poisoning The cache key omits the host or a tenant header, so one tenant’s or an attacker’s response reaches others Key on host and tenant; Cache-Control: private on tenant pages
    Client tenant header The caller adds X-Tenant-Id: t_9c21 Strip it at the edge; trust only the edge’s copy
    Routing loop Two stamps each say the other owns the tenant Hop count: re-route once, then 503 with Retry-After
    Tenant enumeration Names guessed or read from certificate logs Wildcard certificate; one response for unknown and deleted hosts; rate-limit misses

    Host-header injection deserves a closer look. In PortSwigger’s password-reset poisoning, an attacker requests a reset for the victim with Host: evil.example, and an app that builds the emailed link from the Host header sends a genuine email whose link, and secret token, point at the attacker. PortSwigger’s fix is to read the domain from configuration, but a multi-tenant app has a domain per tenant, so make the catalog the allow-list: reject hosts not in the map, and build links from the tenant’s canonical hostname.

    Certificate logs leak names. Certificate Transparency logs are public, so a certificate per tenant subdomain publishes your customer list; a wildcard publishes only *.app.example.

    Going deeper: failure modes

    A stale catalog. If pushes stop reaching one router, it keeps routing on v41: new tenants get 404, and moved tenants bounce through 421 on every request. Alert when any router’s copy falls minutes behind, and keep the new location in each 421 so bounces land even while the catalog is down.

    Split brain during a move. If the catalog flips before stamp-07 stops writing, both stamps accept Acme’s writes and the data diverges. Authority must change at the stamps, in order: stamp-07 freezes, stamp-12 takes over, and only then does routing flip. An ownership epoch, raised by each move and checked on every write, enforces the order.

    A hot tenant at the router. Routers serve every stamp, so a Big Billion Days-style sale or a flood aimed at one tenant’s domain burns router capacity for all. Rate-limit per tenant at the edge, before routing, and give a whale its own router fleet if needed, as you would its own stamp (noisy neighbours and fairness).

    Custom-domain certificate expiry. Renewal fails quietly when a customer repoints its DNS, deletes the CNAME or adds a CAA record that excludes your CA; weeks later only that tenant’s users see a full-page browser error. Track days to expiry per hostname, recheck DNS and CAA daily, and warn the tenant early.

    Cheat sheet

    Topic Remember
    Hints and proof Host, path, client headers are hints; token claim, API key, client certificate are proof
    Subdomains One wildcard DNS record and one wildcard certificate per stem; a certificate wildcard matches one label
    Custom domains Fresh TXT value per attempt, CNAME to you, CAA check, ACME per domain, SNI picks the certificate
    ACME challenges HTTP-01 for CNAMEd domains; DNS-01 for wildcards or early issuance; TLS-ALPN-01 on 443
    Let’s Encrypt limits 300 orders per 3 h per account ≈ 2,400 a day; 50 per registered domain per week; ARI renewals exempt
    Cookies Host-only, __Host- prefix; tenant content on a separate domain
    Catalog 200k × 300 B ≈ 60 MB: push the whole map; keep the last good copy if the catalog is down
    DNS or gateway DNS: no hop, slow moves, host only. Gateway: one hop, moves in seconds, any signal
    Tenant move Moving → freeze → flip → push → 421 and one retry → reconnect sockets → forget
    Attacks Catalog as host allow-list, strip client tenant headers, cache key includes host, re-route once

    How to say it in the interview

    “Every request answers two questions: which tenant, and where it lives. Browsers get a subdomain per tenant behind one wildcard DNS record and certificate; custom domains add a TXT ownership check and an ACME certificate per domain, picked by SNI. The host is only a hint: the token’s tenant claim is the proof, and the edge strips client tenant headers. Placement comes from a catalog owned by the control plane. At 200,000 tenants and 300 bytes each it’s 60 MB, so I push the whole map into every router and keep the last good copy on disk: if the catalog is down, onboarding waits but no request fails. A move is freeze, flip, push, and the old stamp answers 421 so the router retries once.”

    Why not let the client send the tenant ID? A client can send any ID it likes. Use hosts and paths for routing and branding, take the tenant from the signed token, and strip X-Tenant-Id at the edge so services only see the one your edge set.

    How would you onboard 10,000 custom domains? One workflow per domain: TXT and CAA checks, then an ACME order using HTTP-01 through the customer’s CNAME. At 300 orders per 3 hours that takes about 4 days, so queue them or request a higher limit, and renew through ARI, which is exempt from the limits.

    DNS routing or a gateway? DNS per tenant when tenants are few, large and rarely move: no hop, very high availability. A gateway for many tenants, frequent moves, or routing on a token or API key. Often both: DNS picks the region, the gateway the stamp.

    What stops a request bouncing between stamps forever? A hop count: the router re-routes a 421 once; a second miss gets 503 with Retry-After and an alert, because two stamps disagreeing about ownership is a bug.

    Where to go next

    References and further reading

    1. Azure Architecture Center: Map requests to tenants in a multitenant solutionlearn.microsoft.comThe identification signals (domain, path, query string, header, token claim, API key, client certificate), why headers suit APIs rather than browsers, signed API keys, and DNS delays during tenant moves.
    2. Azure Architecture Center: Considerations for domain names in a multitenant solutionlearn.microsoft.comWildcard DNS, regional stem domains, per-tenant CNAMEs that let one tenant move, TXT-based ownership checks, dangling DNS defences and CAA records.
    3. AWS Well-Architected: Cell routing (cell-based architecture)docs.aws.amazon.comKeep the router simple with minimal business logic and keep serving other cells when one fails; Route 53 per-tenant records (100% data-plane SLA) or compute with an in-memory map from S3; build the router as cells too.
    4. AWS Well-Architected: Cell migrationdocs.aws.amazon.comClone, flip, redirect, forget, and cross-cell redirects while a mapping is in transition.
    5. Let’s Encrypt: Challenge typesletsencrypt.orgHTTP-01 on port 80 (works for CNAMEd hosting, no wildcards), DNS-01 with _acme-challenge delegation and wildcards, and TLS-ALPN-01 for TLS-terminating proxies.
    6. Let’s Encrypt: Rate limitsletsencrypt.org300 new orders per account per 3 hours, 50 certificates per registered domain per week, 5 failed authorizations per name per account per hour, ARI renewals exempt, and public Certificate Transparency search.
    7. MDN: Set-Cookie headerdeveloper.mozilla.orgHost-only cookies, Domain scoping to every subdomain, why a public suffix such as github.io can’t be a cookie domain, and the __Host- prefix rules.
    8. RFC 9110: HTTP Semantics, §15.5.20 421 Misdirected Requestrfc-editor.orgWhat 421 means (no authoritative response for the target URI) and that a client may retry it on another connection even for non-idempotent methods.
    9. PortSwigger Web Security Academy: HTTP Host header attacksportswigger.netHost-header injection, password-reset poisoning, cache poisoning and override headers such as X-Forwarded-Host, and the defences: configured domains and a host allow-list.
    10. Shopify Engineering: A pods architecture to allow Shopify to scaleshopify.engineeringSorting Hat in the load balancers matches each request to a pod and adds a header that application servers use to pick the datastore.