skip to content

Before repointing DNS for shop.example.com at a new server, how would you use `curl` to send a genuine HTTPS request for that hostname to one chosen IP address, and why is `--resolve` better than sending the URL as an IP with a `Host` header?

level: middleimportance: nice to knowfreq 38%

answer

  1. the name matters at two layers
  2. SNI is chosen before any HTTP
  3. certificate is checked against the name
  4. seed curl's own DNS cache
  5. --resolve host:port:address

basics

~20 s

Use curl --resolve shop.example.com:443:203.0.113.10 with the normal https URL. It pins the connection to that address while the URL, Host header, TLS SNI and certificate check all still use the real hostname, so the test matches what a real client will do after cutover.

solid answer

~40 s

Run `curl -v --resolve shop.example.com:443:203.0.113.10 https://shop.example.com/health`. The `--resolve` option pre-seeds curl's own name cache with an entry for that host and port, so the TCP connection goes to the address you chose while every other part of the request stays authentic: the request line and `Host` header carry the real hostname, TLS SNI advertises it, and certificate validation checks the presented certificate against it. The alternative — `curl -H 'Host: shop.example.com' https://203.0.113.10/health` — breaks exactly the checks you wanted: the TLS layer sees an IP literal, so the certificate almost never matches, and you end up adding `-k` to silence it. At that point you have stopped testing whether the new server is correctly configured. Note that `--resolve` entries are port-specific, so pin `:80` too if you are testing a redirect.

code

bash · 4 lines
bash
curl -v --resolve shop.example.com:80:203.0.113.10 \
     --resolve shop.example.com:443:203.0.113.10 \
     -o /dev/null -s -w '%{remote_ip} %{http_code} %{time_appconnect}\n' \
     http://shop.example.com/health

go deeper

for a junior

Know that curl --resolve lets you send a request for a hostname to an address you pick, without editing any system file, and that the URL keeps the real hostname.

for a middle

Explain the two places the hostname is used — TLS SNI for certificate selection and the HTTP Host header for virtual-host routing — and why an IP-literal URL breaks the first one.

for a senior

Show that you use pinning as a pre-cutover gate: exercise every pool member and both ports under the production name, verify the certificate rather than bypassing it with -k, and capture timings for comparison.

for a principal

Frame it as release safety: define what evidence must exist before a DNS change is allowed, and note that DNS cutovers are slow and hard to reverse, which is why the verification has to happen beforehand.

## The problem this solves You are about to move `shop.example.com` to a new server at 203.0.113.10. Before you touch DNS you want the same answer a real browser will get afterwards: does that machine serve the right site, on the right hostname, with a valid certificate? You cannot simply request the IP, because a modern HTTPS server distinguishes sites by name at two separate layers, and both must see the real hostname. ## The two layers that care about the name - **TLS SNI.** The client puts the hostname into the ClientHello so the server knows which certificate and virtual host to present. Certificate selection happens *before* any HTTP is exchanged. - **The HTTP `Host` header** (or `:authority` on HTTP/2), which selects the virtual host once the connection is encrypted. A name-based server needs both. Change either and you are exercising a different code path from the one your users will hit. ## What --resolve does ```bash curl -v --resolve shop.example.com:443:203.0.113.10 \ https://shop.example.com/health ``` The syntax is `HOST:PORT:ADDRESS`. curl inserts that entry into its internal DNS cache before the transfer starts, so when it resolves `shop.example.com` for port 443 it uses your address and never asks a resolver. Everything downstream is untouched: SNI carries `shop.example.com`, certificate verification compares the certificate's SAN list against `shop.example.com`, and the `Host` header is the real one. A pass means the new server is genuinely ready. Details worth knowing: - The entry matches **only** the port you name. Testing an HTTP-to-HTTPS redirect needs two entries, one for `:80` and one for `:443`. - You may pass `--resolve` several times, including for hostnames you expect a redirect to land on — otherwise the redirect resolves normally through DNS. - Prefixing the host with `+` sets a timeout-bearing entry; a `-` prefix removes a stale one. A `*` in the host position applies the entry to every hostname, which curl has supported since 7.59.0. - `--connect-to HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT` is the sibling option: it redirects the connection to a different *hostname and port* rather than a fixed address, which is what you want when the target itself is behind another name. ## Why the Host-header trick is worse ```bash # don't do this to test TLS curl -k -H 'Host: shop.example.com' https://203.0.113.10/health ``` This connects to an IP literal. curl does not send SNI for an IP address, so the server falls back to its default virtual host and default certificate — possibly a completely different site. Verification then compares that certificate against the IP, which almost never matches, so the command fails until you add `-k`. And `-k` disables the check that you were trying to perform. You have proved that *something* answers on 443 and nothing about whether the cutover is safe. Over plain HTTP the trick is harmless enough, because there is no certificate and no SNI — but it still leaves you testing one protocol while planning to run another. ## Reading the verbose output With `-v`, the lines that confirm the pin worked are the connection line naming the address you supplied, the TLS lines showing the negotiated protocol and the certificate's subject and SAN, and then the request headers including `Host`. If curl reports a certificate subject-name mismatch, the pin worked and the *server* is misconfigured — which is exactly the finding you were after. ## Where else this pays off The same pinning technique isolates one member of a load-balanced pool ("is it always the same backend that returns 502?"), reproduces a customer's problem when a geo-DNS answer differs from yours, and tests a staging host under its production name. In each case you are separating *which machine answers* from *what identity the request carries*, which is the core skill this leaf is about. ## Pairing it with timing Add `-o /dev/null -s -w '%{remote_ip} %{http_code} %{time_appconnect} %{time_total}\n'` to get the address actually used, the status, and how long TLS took — a compact way to compare an old and a new backend under the same hostname.

  • Your pinned request follows a redirect to another hostname. What happens to the pin?
    Nothing — the entry matches one host and port, so the redirect target is resolved through normal DNS and may land on the old server. Add a second `--resolve` entry for the redirect's hostname and port, or drop `-L` and inspect the `Location` header yourself so you control each hop explicitly.
  • When would you reach for --connect-to instead of --resolve?
    When the destination is best identified by a name rather than a fixed address — for example steering `shop.example.com:443` to `staging-lb.internal:8443`. `--connect-to` redirects the connection to another host and port and lets DNS resolve that target normally, while `--resolve` requires you to know and hardcode a literal address.
  • How does this help when only some users report failures?
    If the hostname resolves to several addresses, pin each one in turn and compare responses. A fault on one pool member, one region's geo-DNS answer, or one node with an expired certificate shows up immediately, whereas an unpinned request keeps hitting whichever address your resolver happens to return.

saying these in an interview costs you the question

  • Just use the IP in the URL with a Host header
  • Adding -k makes the certificate test pass
  • TLS only cares about the Host header
  • --resolve works for every port at once
  • SNI and the Host header are the same thing

context