Zeek Log Format (http.log): Field Reference and Examples
Published
One tab-separated line per transaction, written to a separate log per protocol. In http.log each line is one HTTP request and its response: who asked for which URI on which host, with what client, and what the server returned.
Sample record
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path http
#open 2024-01-15-09-00-00
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types
#types time string addr port addr port count string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string]
1705312800.100000 CbCD23bcEFG 203.0.113.10 52100 10.0.0.50 80 1 GET api.example.com /api/v1/products - 1.1 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 0 4096 200 OK - - - - - - - FbCD001 products.json application/json - - - From the LogTriage parser test corpus — synthetic data, no real hosts or users.
Field reference
| Field | Type | Meaning | Why it matters for security |
|---|---|---|---|
| tse.g. 1705312800.100000 | time (epoch seconds, fractional) | When Zeek saw the request begin. | Convert from epoch seconds before correlating with web server or firewall logs; the fractional part lets you order requests that arrive in the same second. |
| uide.g. CbCD23bcEFG | string | Zeek's unique identifier for the TCP connection the request travelled over. | The pivot between logs — the same uid appears in conn.log, files.log, ssl.log and notice.log for that connection, so one suspicious request can be expanded into the whole session. |
| id.orig_he.g. 203.0.113.10 | addr | Address that originated the connection (the client). | Group by this to find one source probing many paths; behind a proxy or load balancer it is the proxy, not the real client. |
| id.orig_pe.g. 52100 | port | Client source port. | Many distinct source ports from one address in a short window means many separate connections, typical of scripted tooling. |
| id.resp_he.g. 10.0.0.50 | addr | Address that received the connection (the server). | Identifies which of your hosts was targeted when one sensor covers several web servers. |
| id.resp_pe.g. 80 | port | Server port the request was sent to. | HTTP seen on unexpected ports can indicate an admin interface or a service that was not supposed to be reachable. |
| methode.g. GET | string | HTTP request method. | POST and PUT to authentication or upload endpoints are where credential stuffing and webshell drops appear. |
| hoste.g. api.example.com | string | Value of the Host header. | Requests with an internal hostname, an IP literal or a host that does not belong to the server point at direct-to-IP scanning or vhost probing. |
| urie.g. /api/v1/products | string | Request URI, including the query string, as sent on the wire. | The main detection surface — SQL keywords, traversal sequences and sensitive-file paths such as /.env or /admin/config.php show up here, URL-encoded. |
| referrere.g. https://app.example.com/dashboard | string | Value of the Referer header, if present. | Real browser navigation usually carries a referrer; long runs of requests with no referrer suggest a crawler or a script. |
| user_agente.g. sqlmap/1.7.8#stable (https://sqlmap.org) | string | Value of the User-Agent header. | Tool strings and generic HTTP libraries identify automation, but the header is attacker-controlled, so absence of a tool string proves nothing. |
| request_body_lene.g. 0 | count | Number of bytes of request body Zeek saw. | Identical body lengths on repeated POSTs to a login endpoint indicate a loop submitting the same form shape with different credentials. |
| response_body_lene.g. 4096 | count | Number of bytes of response body Zeek saw. | Compared across requests it separates rejected probes from responses that actually returned content. |
| status_codee.g. 200 | count | HTTP status code the server returned. | A wall of 404s from one source is enumeration; a 200 on a path that should not exist, such as /.env, means something was served. |
| resp_mime_typese.g. application/json | vector[string] | MIME types of the response bodies, as identified by Zeek's file analysis rather than taken from the Content-Type header. | text/plain or application/json returned for a config or credential path tells you data left the server; multiple values are joined with the set separator. |
What it records
Zeek watches network traffic and writes one tab-separated line per transaction, split across per-protocol logs. In http.log each line is a single HTTP request and the response to it: the connection tuple (id.orig_h, id.orig_p, id.resp_h, id.resp_p), the method, host and URI, the client’s user agent and referrer, the sizes of the request and response bodies, the status code, and the MIME types Zeek’s file analysis identified in the response. trans_depth numbers the requests within one connection, so a keep-alive session produces several lines sharing a uid.
The file is self-describing. The #fields and #types lines name every column and its Zeek type, and #separator, #set_separator, #empty_field and #unset_field declare how values are encoded. #open and #close bracket the period the file covers.
Where to get it
On a sensor, Zeek writes logs under its install prefix (commonly /opt/zeek or /usr/local/zeek, depending on how it was built). Under ZeekControl the live files sit in logs/current and are rotated and compressed into dated directories, so a search across yesterday must read the archive, not only the current file. Running zeek -r capture.pcap over a packet capture produces the same set of logs in the working directory, which is the quickest way to generate http.log from evidence you were handed.
For shipping to a SIEM, Zeek can emit JSON instead of TSV (redef LogAscii::use_json = T, or one of the JSON streaming packages). Distributions that bundle Zeek, such as Security Onion, place the logs in their own directory layout.
Parsing pitfalls
- Dashes are not data. A
-means the field was unset and(empty)means an empty string, exactly as the header declares. Treat them as absence, not as values. - Read the header, not the position. Scripts and packages add columns, so column 10 is not always
uri. Parse#fieldsfor every file. - Multi-value columns. Types such as
vector[string]andset[enum]—resp_mime_types,tags,orig_fuids— hold several values joined by the set separator, a comma by default. - Epoch timestamps.
tsis seconds with a fractional part, not milliseconds; JSON output has its own timestamp setting, so the same field can arrive as a float or as a formatted string. - The URL is split.
hostanduriare separate columns; detections looking for a full URL have to concatenate them, andurikeeps the attacker’s URL encoding (%20and friends) intact. - Field names contain dots.
id.orig_his a literal column name; in JSON output the key keeps the dot rather than becoming a nested object in every writer, so test your parser against real output. - Credentials. The
usernameandpasswordcolumns only ever cover HTTP basic authentication, and password capture is off unless it has been deliberately enabled.
Detection ideas
- SQL injection probing: SQL keywords or encoded equivalents (
UNION,SELECT,--) insideuri, often with astatus_codeof 200 and a tool string inuser_agent. - Sensitive-file recon: requests for
/.env,/admin/config.php,/wp-admin/install.phpand similar paths — and treat anystatus_code200 on them as an exposure, confirmed byresp_mime_typessuch astext/plain. - Credential stuffing: repeated POSTs to one authentication URI from a single
id.orig_h, with identicalrequest_body_lenand a run of 401 responses. - Scripted clients: generic library user agents on paths a browser would reach, or a crawler user agent claimed by an address that does not belong to that crawler.
- Enumeration: one
id.orig_htouching many distincturivalues with mostly 404s in a few minutes.
These are the checks behind the Zeek HTTP web attack recon rule, and the browser log analyzer runs them locally on an http.log without uploading it.
Detections and guides for Zeek
Frequently Asked Questions
- How do I get from one http.log line to the rest of the traffic?
- Use the uid. Every Zeek log that describes the same connection carries the same uid, so grepping that value across conn.log, files.log, ssl.log and notice.log gives you byte counts, duration, extracted files and any notices for exactly that session.
- Does Zeek record request and response bodies?
- No. http.log stores only request_body_len and response_body_len plus the MIME types that file analysis identified. POST parameters and response content are not in the log unless you separately enable file extraction, so a login attempt shows its size but not the credentials used.
- Why are some columns a single dash or the text (empty)?
- Because the file tells you how it encodes missing data, and both markers mean something different. The header lines declare them — in the sample above
- Can I rely on the column order staying the same?
- Not across versions or deployments. Site scripts and packages can add or remove fields, and the order follows whatever is loaded, which is why every file declares its own
Analyze Zeek logs
LogTriage auto-detects this format, enriches every IP, user agent and hash with threat intelligence, and writes an incident report mapped to MITRE ATT&CK.