A HAR file answers the question "what exactly did the browser do", after the fact and on someone else's machine. That makes it the standard attachment for performance debugging and support tickets, and also one of the most sensitive files a developer casually shares. This page covers both halves: reading the capture properly, and cleaning it before it travels.

What a HAR file is

HAR is short for HTTP Archive: one JSON document that records every request a page made, with the full request and response headers, cookies, timings split into phases, sizes before and after compression, and usually the response bodies themselves. The format every browser exports is the HAR 1.2 spec, a W3C draft that never became a formal standard but has been the de-facto interchange format since 2012. The structure is shallow: a log object with a creator, an optional pages array, and an entries array where each entry is one request.

Because it is plain JSON, everything a HAR "shows" in a viewer is directly in the file. There is no magic: entries[4].response.headers is the literal header list, entries[4].timings.wait is the TTFB in milliseconds. Chrome adds a few underscore-prefixed fields the spec allows as extensions, _transferSize, _resourceType, _priority and _initiator being the useful ones, and this analyzer reads them when they exist.

One spec detail trips up most hand-rolled parsers: -1 is the defined value for "does not apply". A request over a reused connection legitimately reports dns: -1, connect: -1, ssl: -1, and the ssl time, where present, is included in connect, so summing all seven phases double-counts the TLS handshake. The waterfall here subtracts it before drawing, which is why the bars add up to the entry total and a naive sum does not.

Exporting a HAR per browser

The mechanics are similar everywhere: open the network panel first, then load the page, then export. A HAR contains only what the panel recorded, so a capture started after the page settled is mostly empty.

  • Chrome and Edge: F12 → Network → reload → the download-arrow icon ("Export HAR"). Turn on Preserve log when the problem spans a redirect or login, otherwise the navigation clears everything before it. Chrome 130 and later export a sanitized HAR by default; the variant with cookies and auth headers has to be enabled in the DevTools preferences.
  • Firefox: Network tab → reload → gear icon → Save All As HAR. Firefox also lets you copy a single request as HAR, handy for a one-request bug report.
  • Safari: enable the Develop menu in the settings, then Web Inspector → Network → Export on the right of the tab bar.

Two capture habits save a round of "please re-record": disable the cache (checkbox in every browser's network panel) when you are debugging first-visit performance, and keep it enabled when you are debugging what returning users experience, because those are two different waterfalls. And reproduce the problem once, not five times, or the analyzer will honestly report every asset as "fetched more than once".

How to read the report

Drop the .har file anywhere on the panel, or paste its JSON. Everything is parsed in this tab; with 50 MB exports the rows render in batches of 250 so the page stays responsive, and the counter above the waterfall always states how many of the total are currently drawn.

  1. The stats strip answers the first questions: how many requests, how much crossed the wire versus how big the content really is, the total capture time, the slowest single request, how many domains were involved, and how many responses failed.
  2. The waterfall is the timeline. Each bar starts where the request started relative to the first one and is segmented into the timing phases. Click a row for the full detail: headers, query parameters, the per-phase breakdown, the compression rate, and initiator and priority where the export has them.
  3. Filters and sorting narrow it down: free text over the URL, one domain, a resource type, a status class. Sorting by ttfb is the fastest way to find the backend problem, sorting by largest the fastest way to find the bandwidth problem.
  4. Findings list what we would look for by hand: failed requests, redirect chains, text that travelled uncompressed, URLs fetched twice, slow TTFBs, static assets without cache headers and oversized images.

The waterfall phases, decoded

Every entry's time splits into up to seven phases, and each one points at a different bottleneck. Reading them is the core skill of waterfall debugging:

PhaseWhat it measuresWhen it is big
blockedWaiting in the browser's queue before sendingHTTP/1.1 connection limits (6 per host), low priority, proxy negotiation
dnsResolving the hostnameFirst contact with a domain; many third-party domains multiply it
connectTCP handshakeHigh latency to the server; every new connection pays it
sslTLS handshake (inside connect)Same, plus missing TLS session resumption or an OCSP stall
sendWriting the requestLarge POST bodies on a slow uplink; near zero otherwise
waitFirst byte of the response (TTFB)Server think time: database queries, cold starts, no origin cache
receiveDownloading the bodyBig or uncompressed payloads, slow downlink

The shape of the whole waterfall matters more than any single bar. A staircase of short bars, each starting when the previous ends, is a dependency chain: HTML loads a script, the script requests JSON, the JSON triggers images. Nothing in that chain is slow, and the page is slow anyway; the fix is preloading or restructuring, not a faster server. A comb of parallel bars all starting at the same offset means the browser discovered everything at once, which is what you want. And a bar that starts late for no visible reason is usually lazy-loaded JavaScript discovering the resource late.

The dns/connect/ssl trio appears once per connection, not once per request, so seeing it repeated on the same host means connections are not being reused. On a healthy HTTP/2 site the trio shows on the first request per domain and then almost never again; each extra third-party domain buys the whole handshake again, which is the measurable cost of that fifth analytics vendor.

Debugging a slow TTFB

The wait phase is the one you cannot fix from the frontend: the request is fully sent, nothing has come back, and everything in between is network latency plus server processing. The debugging order that has worked for us:

  1. Separate latency from processing. Compare the TTFB against the connect time of the same entry. The TCP handshake is one round trip, so it approximates the pure network distance. A 900 ms wait behind a 30 ms connect is a slow server; a 350 ms wait behind a 300 ms connect is mostly geography, and the fix is a CDN or an edge region, not query tuning.
  2. Check whether it is one endpoint or all of them. Sort by ttfb. If only /api/search is slow, it is that query. If every dynamic response is slow but static files are fast, look at the application server: cold starts on serverless platforms, connection pool exhaustion, a saturated event loop.
  3. Check the first request separately. The document's TTFB includes things asset requests never see: session lookup, server-side rendering, uncached page generation. A slow document with fast assets means the HTML generation is the problem.
  4. Look for the redirect tax. A chain of 301s before the real document adds a full round trip each, and on fresh connections a handshake per hop. The findings list flags chains; the fix is pointing the origin link directly at the final URL.

What a HAR cannot tell you is why the server took 900 ms, only that it did. The moment the waterfall points at one endpoint, the investigation moves to server-side tracing; the HAR's job was to name the endpoint and rule out the network, and it does that well.

transferSize vs content size

Every response has two sizes, and the gap between them is the compression story. content.size is the decoded body, the bytes your JavaScript parser or image decoder actually saw. _transferSize is what crossed the network: response headers plus the body as encoded, gzip'd or brotli'd. The detail view computes the ratio per request, and the stats strip totals both, so the "1.9 MB transferred / 6.4 MB uncompressed" pair tells you at a glance how much compression is already doing.

The interesting rows are the ones where the two sizes are nearly equal on text content. HTML, JavaScript, CSS, JSON and SVG compress to a quarter or a third of their size almost for free; a 180 KB script with a 180 KB transfer size means the server never compressed it, and the findings section flags exactly that combination (200 status, text type, no Content-Encoding, wire size at or above content size). It is among the most common wins in real captures, typically one config line on the server or CDN.

Cached responses muddy the numbers: a request answered from disk cache reports a transfer size of 0 or a few hundred header bytes while the content size stays at the full body. That is not an anomaly, it is caching working, and it is why the analyzer never counts negative or missing sizes into the totals. Firefox additionally omits _transferSize in some versions, in which case the fallback is headersSize + bodySize, which is accurate for uncompressed HTTP/1.1 and an approximation everywhere else.

HAR files are full of secrets

This deserves its own section because the failure mode is so quiet. A HAR of a logged-in session contains, in plain text: every cookie the browser sent, including the session cookie; every Set-Cookie the server answered; Authorization headers with bearer tokens; API keys in query strings; POST bodies, passwords included if you captured a login; and response bodies, which echo tokens more often than you would think. Vendors have had real incidents from exactly this vector, support tickets with attached HARs, notably the 2023 Okta support system breach where attackers harvested session tokens from uploaded HAR files.

Two consequences. First: analyzing a HAR should not require uploading it anywhere, which is the reason this tool runs entirely in your tab; the file is read locally and no request carries it away. Second: when the file has to travel, to a colleague, a vendor, a ticket system, sanitize it first. The sanitize & download button empties the cookie arrays, redacts Cookie, Set-Cookie, Authorization and the usual key-carrying headers, and rewrites token-style query parameters both in the parsed parameter list and inside the URL strings, since HAR stores them in both places. The structure stays valid HAR 1.2, so the recipient's tooling still works, and the loaded report tells you exactly how many items were touched.

The sanitizer is deliberately conservative about what it does not do: response bodies stay untouched, because rewriting them would destroy the payloads the recipient may need to see. If the session token appears inside a JSON response body, search the file for it before sending. And the pattern list errs toward over-redacting, so a harmless ?key=piano parameter will come out redacted too; that trade is intentional.

If the capture's headers raise follow-up questions, the neighbouring tools take over: the Cache-Control analyzer computes who caches a response you found suspicious, and the Set-Cookie parser explains every attribute of the cookies a response sets.

Reading a HAR file

How do I export a HAR file from Chrome DevTools?

Open DevTools with F12, switch to the Network tab, reload the page with the recording running, then click the download-arrow icon in the panel toolbar ("Export HAR"). Two things matter before you reload: enable "Preserve log" if the flow spans a redirect or a login, otherwise the entries before the navigation are wiped, and start recording before the first request, because DevTools only captures what it saw. Since Chrome 130 the export is sanitized by default, meaning cookies and Authorization headers are stripped; a "with sensitive data" variant can be enabled in the DevTools settings under Preferences, and support teams often ask for that one because auth bugs are invisible without it.

How do I create a HAR file in Firefox or Safari?

Firefox: open the Network tab in the developer tools, reload, then click the gear icon on the right of the toolbar and pick "Save All As HAR", or right-click any request row and choose the same. Safari: enable the Develop menu under Settings, then in Develop open the Web Inspector, go to the Network tab, reload, and use the "Export" button on the right of the tab bar (Safari writes the same HAR JSON with a .har extension). Edge works like Chrome, same icon, same panel.

Do HAR files contain passwords, cookies or tokens?

Yes, routinely. A HAR records every request with its full headers, so session cookies, Authorization bearer tokens and API keys are all in there in plain text, and POST bodies are included too, which is where a submitted password lands. Response bodies are embedded as well, so a JSON response that echoes a token stores a second copy. Treat a fresh HAR like a session backup: anyone who has the file can usually replay your logged-in session until the cookies expire.

How do I remove sensitive data from a HAR file before sharing it?

Strip four things: the cookies arrays on every request and response, the Cookie and Set-Cookie headers, the Authorization and Proxy-Authorization headers, and token-style query parameters (token, api_key, signature and friends) both in the queryString array and inside the URL string itself, because HAR stores the parameters twice. Doing that by hand in a 30 MB JSON file is hopeless, which is why this analyzer has a sanitize & download button: one click redacts all of the above locally and saves a .sanitized.har you can attach to a ticket. What it deliberately leaves alone are response bodies, so if an API echoes a secret in its payload, search the file for it before sharing.

Is it safe to upload a HAR file to an online analyzer?

With most tools you are uploading your active session cookies and auth tokens to someone else's server, so only do it with a sanitized file or a capture of a logged-out flow. Google's HAR analyzer and similar sites process the file server-side or at least give no verifiable guarantee. This analyzer parses the file in your browser tab; nothing is uploaded, which you can confirm in the Network panel while using it. The honest general rule: assume any HAR leaving your machine grants access to whatever the captured session could access.

What does the blocked or queueing time in a waterfall mean?

It is time the browser spent holding the request back before sending it, not network time and not server time. The classic causes: the per-host connection limit on HTTP/1.1 (six per host in Chrome and Firefox, so request seven waits for a free slot), the request being queued behind higher-priority resources, waiting for an available TCP connection, or time spent on a proxy negotiation. A page with long blocked phases on many small requests is usually an HTTP/1.1 server that would benefit from HTTP/2 multiplexing, or a page firing dozens of requests at one host at once.

What is a good TTFB?

Google's guidance for the server-response part of Core Web Vitals is under 800 ms for the document, and well-tuned origins sit far below that: a cached page from a nearby CDN edge answers in 20 to 100 ms, a dynamic page with a database query typically in 100 to 400 ms. Above 800 ms users feel the pause, and above 1.5 s the page competes with a full round of coffee. Note that TTFB includes one network round trip by definition, so a user far from the origin has a floor of 100 to 200 ms no matter how fast the server is; that part is fixed by moving the response closer, not by faster code.

What is the difference between transferSize and content size in a HAR file?

content.size is the decoded body, the bytes after decompression; _transferSize is what actually crossed the wire, headers plus the compressed body. A 300 KB JavaScript bundle with content.size 300000 and transferSize 95000 was gzip-compressed to roughly a third. If transferSize is about equal to or larger than content.size on a text response, the response was not compressed at all, which is one of the cheapest performance fixes there is. transferSize can also be 0 or missing when the response came from cache, and Firefox exports omit the field entirely on some versions, which is why analyzers fall back to headersSize plus bodySize.

Why do some values in my HAR file show -1?

In HAR, -1 means "does not apply", not an error. The spec defines it for timings: a request on a reused keep-alive connection has dns, connect and ssl at -1 because no lookup or handshake happened for it, which is the normal case for every request after the first per host. Sizes use it too: headersSize and bodySize are -1 when the exporter could not determine them, common with HTTP/2 where header bytes on the wire are compressed with HPACK and not directly observable. A waterfall renderer should treat -1 as zero-length, not as a missing phase.

Why is my HAR file so large?

Because full response bodies are embedded in the JSON, base64-encoded when binary, so a page with 5 MB of images produces a HAR well past 7 MB from the encoding overhead alone, and every reload with "Preserve log" on appends another page load. To shrink it: capture only the flow you need, clear the log first, and in Firefox you can disable response bodies in the Network settings. Chrome always embeds bodies in the export. For pure timing analysis the bodies are dead weight; the entries, headers and timings that matter are a fraction of the size.

How do I open a .har file?

Three ways. Import it back into a browser: Chrome DevTools has an import icon (up arrow) in the Network panel that restores the full waterfall, Firefox accepts a HAR dropped onto its Network tab. Open it in an editor: it is plain JSON, so any editor works, though a 40 MB single-line file will make some struggle. Or use an analyzer like this page, which adds the parts DevTools does not show side by side: computed compression rates, duplicate detection and findings. If a colleague sent you the file, remember it likely contains their session cookies; treat it accordingly.