Skip to content
ALI HAITHAM·TECH·responds in <1h
  • Homestart here
  • Workcase studies, projects
  • Serviceswhat I will build for you
  • Writingessays, series
  • Traincourses, labs, cohorts
  • Aboutthe engineer behind this site
Sign inStart a project
ALI HAITHAM · TECH
  • Home↗
  • Work↗
  • Services↗
  • Writing↗
  • Train↗
  • About↗
Sign inStart a project
Online
ALI HAITHAM·TECH

Engineering studio. Damascus, GMT+3.

aliyosef.online

Studio

  • Work
  • Writing
  • Training
  • About
  • Contact me

Portal

  • Sign in
  • Open a ticket
  • Track project
  • My account

Resources

  • Docs
  • Status
  • Changelog
  • Brand kit
  • Privacy
  • Terms

Newsletter

Field notes and tech news. Weekly. No fluff.

Free. Unsubscribe anytime.

Find me elsewhere
© 2026 Ali Haitham Yosef. All rights reserved.Hand-built in React 19. No frameworks of frameworks.Last deployed · 2026-05-08All systems operational
  1. Home/
  2. Writing/
  3. Performance
Performance

The forgotten cost of connection setup

TLS, DNS, TCP. The hidden 600ms before any of your code runs. Where to claw it back.

December 6, 2025·7 min·by Ali Yosef

Loading essay…

ShareXLinkedInRSS
Get the next essay in your inbox.New posts every Sunday morning. Engineering, in plain language. Free, unsubscribe anytime.Subscribe→
AY
Ali Haitham Yosef

Engineer and writer. Builder of multi-tenant platforms, sync engines, and quietly reliable systems. 600+ shipped projects since 2015.

SubscribeStart a project→

Read next

  • 2H
    Jan 2, 2026·Performance

    The 200ms target on bad networks

    What you can guarantee on 2G, what you cannot, and why most performance budgets are wishful thinking.

    12 min
  • IS
    Dec 22, 2025·Performance

    Indexing strategies for analytical queries

    When the right index makes the query 200× faster, and when adding an index makes everything worse.

    10 min
  • MD
    Dec 14, 2025·Performance

    Why we measure on real devices

    A four-year-old Android phone tells the truth. Your dev machine tells you what you want to hear.

    6 min
←Back to all essays

A request to your server takes 600 ms. Your server processes it in 50. The other 550 are split between TLS, DNS, and TCP, all happening before your code runs. Most performance writeups treat this as a fixed cost. It is not. Here is where the time actually lives, and where to claw some of it back.

The breakdown#

A cold HTTPS request from a phone on cellular to a server in another region:

DNS resolution            70 ms   (cold cache)
TCP three-way handshake   80 ms   (one round trip)
TLS 1.3 handshake        180 ms   (one round trip)
HTTP request              5 ms
Server processing       varies
HTTP response             5 ms
                       ────────
                      ≥350 ms before any data is exchanged

That number is for a healthy connection in average conditions. Cellular and rural networks add 100-300 ms of round-trip on top. We have measured 1.4 seconds of pre-data overhead on real-world Egypt- to-Frankfurt connections during peak hours.

The point is that the user-visible "this app is slow" is often nothing the application code did wrong. It is the cost of saying hello.

What is fixed and what is not#

Three of the four steps are reducible.

DNS#

A DNS lookup is one or two round-trips to a recursive resolver. Mitigations:

  • Pre-warm DNS at app launch by triggering a hostname lookup for the API endpoint. The lookup happens in parallel with other boot work; by the time the user taps anything, DNS is already cached.
  • Use a CDN-backed DNS (Cloudflare, Route 53) that has POPs near the user. Geographic proximity dominates DNS latency.
  • Hard-code A records for static endpoints in the app, with a sane TTL fallback. Eliminates DNS entirely for the API host.

We use the first two. We do not use the third because we want to be able to change DNS without an app update.

TCP#

TCP three-way handshake is one round trip and is unavoidable on a cold connection. Mitigations:

  • HTTP/2 over a long-lived connection. One TCP handshake at boot, then every request reuses the same connection. Saves the handshake on every subsequent request.
  • HTTP/3 / QUIC. TCP-and-TLS-in-one over UDP. The first request is roughly the same cost as HTTP/2; subsequent requests after a network change (cellular handover) reconnect faster because QUIC doesn't need to re-handshake.

We use HTTP/2 with a connection pool. HTTP/3 is on our list but the proxy in front of our API doesn't support it cleanly yet.

TLS#

TLS 1.3 is one round-trip. TLS 1.2 was two. Make sure you are on 1.3 end-to-end. The other moves:

  • Session resumption. A device that has connected before can reuse the session ticket and skip the handshake. Saves ~150 ms on reconnects.
  • 0-RTT (TLS 1.3 feature). The first request data goes out with the resumed handshake, no extra round-trip. Has security caveats (replay attacks); only safe for idempotent requests. We use it for reads, not writes.

Session resumption is on by default in modern stacks. 0-RTT is opt-in on most reverse proxies; check yours.

Where to claw the time back#

In rough order of how much we got back per hour of work:

Connection pooling (biggest win)#

Default Dart HttpClient and Java's HttpURLConnection keep connections open. Our naive HTTP wrapper was creating a new client per request. Reusing one client across the app saved a full TCP+TLS round-trip on every request after the first. Average request latency dropped 200 ms.

// before
Future<Response> get(String url) async {
  final client = HttpClient();
  // ... use client
  client.close();
}

// after
class Api {
  static final _client = HttpClient(); // shared, app lifetime
  Future<Response> get(String url) async {
    // ... use _client
  }
}

Pre-connect on app boot#

When the app starts, we issue a HEAD request to our API root. Doesn't transfer any data, but warms DNS, opens the TCP connection, and completes the TLS handshake. By the time the user does anything real, the connection is ready.

Future<void> warmConnection() async {
  try {
    await _client.head(Uri.parse('https://api.aliyosef.online/health'));
  } catch (_) {
    // Best effort. Network might be down. Don't block boot.
  }
}

This is invisible to the user but makes the first real request feel instant. Cost: one extra request per session, ~5 KB. Worth it.

Switch to a closer region#

If your users are in one geographic region and your server is in another, no amount of protocol tweaking will save you from the speed of light. We moved our API from EU-West to ME-Central for our Saudi and Syrian users. Round-trip latency went from 220 ms to 30 ms. That single move saved more time than every other optimization combined.

The round-trip floor

Light in fibre travels about 200,000 km/s. Riyadh to Frankfurt and back is 8,400 km. The physical minimum round-trip is ~42 ms, before any router, switch, or processing. Your latency cannot go below this. Pick your region accordingly.

Compress everything#

Gzip on responses is table stakes. Brotli is better but not universally supported. Both reduce wire bytes, which reduces the multi-RTT cost on slow links. Set Accept-Encoding: gzip, br on the client and configure the server to compress responses above ~1 KB.

Don't do connection setup on the critical path#

The smartest fix is to not make the round-trip in the first place, which is the entire point of offline-first design. The first request after boot can be answered from local cache; the network is only used to refresh the cache, in the background.

What we deliberately did not do#

Three optimisations we considered and rejected.

TCP Fast Open#

Saves one round-trip on TCP handshake but is poorly supported, especially in middleboxes (corporate proxies, cellular carrier gateways). Net negative for our user base; you may have a different profile.

Multiplexing across many small services#

Spreading the API across many hostnames means many DNS lookups, many TLS handshakes, many TCP connections. We consolidated to one hostname behind a reverse proxy and route by path. Saves on every cold connection.

Aggressive HTTP/2 server push#

Server push had promise on paper but rarely makes things faster in practice. The browser/client preload signals are easier to use and better tuned. We don't use server push.

What we measure#

Two specific numbers:

Time-to-first-byte (TTFB), broken down#

Every API call records DNS, TCP-connect, TLS, request-write, server-wait, response-read. The breakdown matters: if DNS is the biggest chunk, the fix is different from "TLS is the biggest chunk." Aggregate TTFB hides which part to optimise.

Connection reuse rate#

Of all our requests, what percentage are made over a connection that was already open? Should be > 95 % steady-state. If it drops, something is killing connections (proxy timeouts, server limits, client misuse).

The boring conclusion#

Most "the app is slow" complaints are about connection setup, not processing. The fixes are mostly structural: keep one connection open, pre-warm it at boot, put your server geographically close to your users, compress everything. None of it is glamorous; all of it moves the numbers.

Browse the performance topic for the rest of the series, or jump into the sync engine series for a different take on what bad networks do to architecture.