Skip to content

NOAA hurricane track data (HURDAT2 / NHC)

Verified Jun 9, 2026 · tested with live no-key pull of the Atlantic HURDAT2 best-track file (nhc.noaa.gov, hurdat2-1851-2023)

View this page as raw Markdown (.md)

climatenatural-disastersweatherfreeno-api-keyevent-datadata:noaa-hurricane

HURDAT2 (HURricane DATabase, version 2) is the post-season “best track” archive maintained by NOAA’s National Hurricane Center (NHC). For every tropical and subtropical cyclone it records 6-hourly entries (plus extra entries at landfall and intensity peaks) with maximum sustained wind, central pressure, and the 34/50/64-knot wind radii by quadrant. There is a separate Atlantic file (since 1851) and a separate Northeast and North-Central Pacific file (since 1949). Used in, for example, Kruttli, Roth Tran & Watugala, which identifies 37 hurricane landfalls (1996-2019), eye location at 6-hour intervals, and (separately) wind-speed probability forecasts and seasonal outlooks.

The paper also uses real-time NHC forecast and advisory products (wind-speed probability forecasts and seasonal hurricane outlooks). Those operational products are distinct from the post-season HURDAT2 best track; see the gotchas.

The directory https://www.nhc.noaa.gov/data/hurdat/ lists both the Atlantic and the Pacific files along with their issue dates. The dated filename changes with each annual reanalysis, so list the directory and pick the current file rather than hardcoding a name.

Terminal window
# Atlantic best-track file (1851-2023 issue), no key.
# The dated filename changes with each annual reanalysis; list the directory first.
curl -sL "https://www.nhc.noaa.gov/data/hurdat/hurdat2-1851-2023-051124.txt" \
-o hurdat2_atlantic.txt

The file is comma-delimited text with two line types. A storm header line has three fields (storm ID, name, and the number of data lines that follow); each following line is one observation. Parse it as a state machine, not as a flat CSV:

import numpy as np
import pandas as pd
def parse_coord(s):
s = s.strip()
val = float(s[:-1])
if s[-1] in ("S", "W"):
val = -val
return val
rows = []
with open("hurdat2_atlantic.txt") as f:
storm_id = storm_name = None
for line in f:
fields = [p.strip() for p in line.split(",")]
# A header line has exactly three populated fields; data lines have many more.
if len(fields) == 4 and fields[3] == "":
storm_id, storm_name = fields[0], fields[1]
continue
if len(fields) < 8:
continue
rows.append({
"storm_id": storm_id,
"storm_name": storm_name,
"date": fields[0],
"time": fields[1],
"record_id": fields[2], # "L" marks a landfall, else blank
"status": fields[3], # HU, TS, TD, ...
"lat": parse_coord(fields[4]),
"lon": parse_coord(fields[5]),
"wind_kt": int(fields[6]),
"pressure_mb": int(fields[7]),
})
df = pd.DataFrame(rows)
# Missing values are coded -999; convert before any aggregation.
df = df.replace(-999, np.nan)

The first storm header in the Atlantic file is AL011851, UNNAMED. To work basin-wide, repeat the pull for the Pacific file from the same directory.

PositionHeader lineData line
1Storm ID (basin letters + cyclone number + 4-digit year, e.g. AL011851)Date YYYYMMDD
2Storm name (or UNNAMED)Time HHMM UTC
3Number of data lines that followRecord identifier (L = landfall, else blank)
4System status (HU hurricane, TS tropical storm, TD tropical depression, others)
5Latitude, e.g. 28.0N
6Longitude, e.g. 94.8W
7Maximum sustained wind (knots)
8Minimum central pressure (millibars)
9+34/50/64-knot wind radii in the four quadrants

Missing values are coded -999.

  • The filename encodes the version and issue date. A name like hurdat2-1851-2023-051124.txt carries the issue date (051124) and changes with each annual reanalysis. Do not hardcode it; list the directory and pick the current file.
  • Two line types in one file. Storm header lines and observation lines have different field counts. Parse with a state machine or a two-pass approach that tracks the current storm, not as a flat CSV.
  • Missing values are -999, not blank. Convert them to NaN before computing means, maxima, or any aggregate, or the numbers will be wrong.
  • Coordinates are strings with hemisphere suffixes. Values like 28.0N and 94.8W must be converted to signed decimals before any distance calculation.
  • Units. Winds are in knots and pressure in millibars. Convert if you need meters per second, miles per hour, or hectopascals.
  • Atlantic and Pacific are separate files. A basin-wide analysis must read both; neither file alone covers all storms.
  • Best track is a post-season reanalysis. It differs from the operational advisories issued in real time. For an identification that depends on what investors or agents knew at the time (as in the citing paper’s real-time forecast use), the best track is not the real-time information set. Keep best-track and operational products distinct.
  • One storm can make several landfalls. The L record identifier marks a landfall, and a single storm can carry more than one such entry. Do not assume one landfall per storm.

Cite NOAA NHC, the HURDAT2 database, the basin file and its issue date, the NHC URL, and the access date, for example: NOAA National Hurricane Center, HURDAT2 Atlantic best-track file (hurdat2-1851-2023-051124), retrieved from https://www.nhc.noaa.gov/data/hurdat/, accessed YYYY-MM-DD. The issue date is in the filename; record it so the pull is reproducible.

Found an error or want a topic covered? Open an issue, use the Edit page link above, or email contact@instituteforautomatedresearch.org. Edits are reviewed before publishing; provenance and accuracy are the point.