When you reconstruct an incident across firewalls, endpoints, and application logs, the timestamps rarely agree.
One source records Oct 10 13:55:36 with no year and no time zone.
A Zeek log two racks over emits 1760104536.225121.
The firewall already speaks 2025-10-10T13:55:36.003Z.
Until every one of those is converted to a single format and time zone, your event timeline is an estimate.
The fix is timestamp normalization: convert each timestamp to ISO 8601 in UTC at the point of collection, before the data reaches your SIEM. This article covers the format, the parsing edge cases that produce wrong times, and a working configuration you can adapt.
Why mismatched timestamps break correlation
Log sources disagree about time in three ways: format, precision, and zone.
A device that uses the original BSD syslog format (RFC 3164) writes a timestamp like Oct 10 13:55:36 - month name, day, and clock time, with no year and no zone.
A web server using Apache’s Combined Log Format writes [10/Oct/2025:13:55:36 -0700].
A Zeek connection log records seconds since the Unix epoch as 1760104536.225121.
Modern structured syslog (RFC 5424) and most JSON logs use 2025-10-10T13:55:36.003Z.
RFC 3164 (BSD syslog): Oct 10 13:55:36 Apache Combined Log: [10/Oct/2025:13:55:36 -0700] Zeek conn.log (epoch): 1760104536.225121 RFC 5424 / JSON: 2025-10-10T13:55:36.003Z
Drop those four into one index and sort by time, and the order comes out wrong. The epoch value sorts as a bare number. The BSD line has no year to anchor it. The offset on the Apache line shifts it by seven hours relative to any reader who assumes UTC. For an analyst tracing lateral movement, a seven-hour error can reverse the apparent order of two events and point the investigation at the wrong host.
Normalization removes the ambiguity. Pick one format, convert everything to it, and your queries, correlation rules, and timelines all run on the same scale.
What ISO 8601 specifies, and where RFC 3339 differs
ISO 8601-1:2019 defines how to represent dates and times as text.
The combined date-time form puts the date first, largest unit to smallest, a literal T between date and time, and a time zone designator at the end:
2025-01-03T14:07:15Z UTC, whole seconds 2025-01-03T14:07:15.003Z UTC, millisecond precision 2025-01-03T16:07:15.003+02:00 same instant, +02:00 offset
The Z stands for UTC ("Zulu").
A numeric offset, such as +02:00, describes local time relative to UTC.
Fractional seconds are optional and can run to whatever precision the source provides.
Most logging standards skip the full ISO 8601 grammar and use RFC 3339 instead, which the IETF published in 2002 as "a conformant subset of the ISO 8601 extended format."
RFC 3339 makes the fields and punctuation mandatory, so every timestamp carries a complete date, time, and zone.
RFC 5424 syslog uses an RFC 3339 timestamp, which is why structured syslog already hands you 2025-01-03T14:07:15.003Z.
The two standards are close but not interchangeable.
ISO 8601 allows week dates such as 2025-W04-6 and ordinal dates such as 2025-003; RFC 3339 allows neither.
Earlier editions of ISO 8601 permitted an hour value of 24; RFC 3339 restricts the hour to 00-23.
With minor edge cases aside (RFC 3339 tolerates lowercase separators and -00:00), every valid RFC 3339 timestamp is valid ISO 8601, but the reverse does not hold.
For logs, target the RFC 3339 profile: extended format, explicit Z or offset, and fractional seconds where applicable.
It parses cleanly in every mainstream language and tool.
The parts that go wrong
Converting between formats is mechanical. The errors come from information that the source never recorded in the first place.
- Missing year
-
BSD syslog omits the year. RFC 5424 states the problem and prescribes the workaround: when an RFC 3164 message is reformatted, "the current year should be added." That guess is right most of the time and wrong across the year boundary. A line generated at 23:59 on December 31 and ingested a minute later in January gets stamped with the new year:
# BSD syslog line generated 2024-12-31, ingested 2025-01-01 Dec 31 23:59:58 host backup: snapshot completed # Collector adds the current year (2025) per RFC 5424 guidance # Result: 2025-12-31T23:59:58 - one year in the future
Any collector that adds "the current year" inherits this, so handle the December-January window explicitly when you parse BSD syslog.
- Missing time zone
-
BSD syslog also drops the zone, so a parser has to assume one. Assume UTC for a device running at +09:00, and every event lands nine hours off. The safe approach is to configure the source’s real zone at the collector rather than letting a default stand.
- Daylight saving transitions
-
When clocks fall back, one local hour repeats. A timestamp of 01:30 on that morning maps to two different UTC instants, and a converter with no extra context picks one of them. Converting to UTC as early as possible - in the input stage, before any offset arithmetic - limits the exposure.
- Precision loss
-
A busy source can emit many events in a second. Truncate to whole seconds, and you lose the ordering within that second. Keep at least milliseconds, and store microseconds when the source provides them.
- Ambiguous epoch units
-
Unix epoch values sidestep the zone question, since epoch time is UTC by definition. The trap is unit:
1301471167,1301471167225, and1301471167225000are the same instant expressed in seconds, milliseconds, and microseconds, and nothing in the number tells you which. Interpret milliseconds as seconds, and the event jumps tens of thousands of years. Confirm the source’s unit before you convert, rather than inferring it from digit count. - Clock skew
-
Normalization fixes how time is written, not whether it is correct. A host whose clock has drifted still produces perfectly valid timestamps that disagree with its neighbors by minutes. RFC 3339 points to the remedy: synchronize hosts to UTC with NTP. Format normalization and clock synchronization solve different failures, so run both.
Normalizing at the collector
The reliable place to normalize is the collection agent, before events fan out to storage and analytics. Below, NXLog Agent parses incoming syslog and rewrites the timestamp to ISO 8601 in UTC.
The parse_syslog() procedure reads either BSD or IETF syslog and sets the $EventTime field.
The agent stores datetime values internally as microseconds since the Unix epoch, so a parsed timestamp is already a zone-aware value ready to reformat.
The strftime() function with the sUTC token converts that value to UTC and renders the Z form:
<Extension syslog>
Module xm_syslog
</Extension>
<Extension json>
Module xm_json
</Extension>
<Input bsd_syslog>
Module im_udp
ListenAddr 0.0.0.0:514
<Exec>
parse_syslog();
$EventTimeOriginal = $EventTime;
$EventTime = strftime($EventTime, 'YYYY-MM-DDThh:mm:ss.sUTC');
to_json();
</Exec>
</Input>
That produces a UTC timestamp such as 2025-01-03T14:07:15.003000Z, whatever the source’s original format.
For sources whose offset is recorded incorrectly, NXLog’s timestamp how-to guide recommends performing the conversion in the input module instance, which reduces the risk of applying the wrong offset during a DST transition.
The same shape works for any source: parse the native timestamp into a real datetime, then format it once on the way out.
A JSON source follows the same pattern: parse the record so the timestamp becomes a datetime field, then apply the same strftime() call before output.
Every feed ends up in a single format regardless of how it came in.
Validate before you trust it
A few checks catch the common mistakes:
- Keep the original timestamp
-
Store the raw source value next to the normalized one. If a conversion turns out wrong, you can reprocess it; if you overwrite it, the evidence is gone.
- Test the year boundary
-
Feed December and January BSD samples through the pipeline and confirm the year resolves correctly.
- Test a DST changeover
-
Run timestamps from the fallback hour and check that they map to the right UTC instant.
- Confirm precision survives
-
Verify that sub-second values are stored intact rather than silently truncated.
Conclusion
Mismatched timestamps corrupt any investigation that spans multiple sources. Convert each one to ISO 8601 in UTC at the point of collection, and the ambiguity is gone. As a result, events sort correctly, correlation rules compare them on a shared timescale, and the incident timeline matches the real sequence. Normalize the format, synchronize the clocks with NTP, and keep the originals. Without all three, a multi-source timeline can’t be trusted.
Visit the NXLog website to learn how NXLog Agent can normalize logs at the point of collection, as part of a complete NXLog Platform deployment.
FAQ
- What is log timestamp normalization?
-
Log timestamp normalization is the process of converting timestamps from different log sources into a single format and time zone. Logs arrive in formats such as BSD syslog, Apache Combined Log Format, and Unix epoch time, each with its own precision and zone handling. Normalizing them - usually to ISO 8601 in UTC - lets you sort, search, and correlate events on a single time scale.
- What timestamp format should logs use?
-
Use ISO 8601 in its RFC 3339 profile: a four-digit year first, a
Tbetween date and time, fractional seconds, and an explicitZor numeric offset. For example2025-01-03T14:07:15.003Z. This format sorts correctly as plain text, removes date ambiguity, and parses without special handling in mainstream languages and tools. - What is the difference between ISO 8601 and RFC 3339?
-
RFC 3339 is a stricter subset of ISO 8601 built for internet protocols. It makes the date, time, and zone mandatory, requires a
Tseparator, and limits the hour to 00-23. ISO 8601 also allows week dates, ordinal dates, and a 24-hour value. Every valid RFC 3339 timestamp is valid ISO 8601, but not the reverse, so for logs, you follow RFC 3339. - Should log timestamps be stored in UTC or local time?
-
Store them in UTC. UTC has no daylight saving transitions, so timestamps never become ambiguous or repeat, and events from sources in different regions sort into the correct order. Convert to UTC at collection time, and keep the original offset only if you need it for forensic context. You can still display local time to analysts later, but the stored value should be UTC.
- How do you handle logs with no time zone or year, such as RFC 3164 syslog?
-
BSD syslog (RFC 3164) omits both the year and the time zone. Configure your collector to use the source’s actual time zone instead of a default, and convert to UTC as early as possible. For the year, the common practice is to add the current one - accurate except across the December-January boundary, where you should handle the rollover explicitly to avoid stamping events a year ahead.
- Does normalizing timestamps fix clock skew between systems?
-
No. Normalization standardizes how a timestamp is written, not whether the clock that produced it was accurate. Two hosts can both emit correct ISO 8601 and still disagree by minutes if their clocks drift. Ensuring accuracy requires time synchronization, which RFC 3339 recommends using NTP for. You need both: synchronized clocks and a normalized format.
- What does an ISO 8601 timestamp with milliseconds look like?
-
A millisecond-precision UTC timestamp looks like
2025-01-03T14:07:15.003Z, where.003is the fractional second andZmarks UTC. The same instant with a two-hour offset is2025-01-03T16:07:15.003+02:00. ISO 8601 allows finer precision too, so microseconds (.003000) and nanoseconds are valid when your source records them.