An ESP32 NTP Stratum 1 Time Server for your home network
This project was born with a simple goal: create a low-budget, Stratum 1 NTP time server solution with no need for periodic Linux OS updates. Rather just set it up and let it do its job.
It uses a GNSS (North America & Europe) receiver as its time source, making it a true Stratum 1 server — meaning it gets its time directly from satellites rather than from another NTP server. Every device on your home network can then synchronize to it for highly accurate local time.
Current Version (3.0+)
- NTPv4 Symmetric-key authentication added for Windows clients using Meinberg and Linux clients using chrony. For more information, please see this document.
- WS2182 RGB LED support added (KY-016 support continues)
Version 2 was a ground-up rewrite which came with a faster, more capable board support and a long list of accuracy, reliability, and convenience improvements over version 1, including:
- Higher accuracy, lower jitter — full use of the GPS module's PPS (Pulse Per Second) pin disciplines the clock for sub-millisecond accuracy, with hardware time stamping and RFC 9769-compatible interleaved responses further reducing jitter for both NTPv3 and NTPv4 requests.
- Faster, more modern hardware — built around the WaveShare ESP32-P4-ETH (or the newer ESP32-P4-WIFI6-ETH), a significant step up from the ESP32-WROOM-32/32E used in v1.
- Broader GNSS module support — still works great with the recommended SparkFun MAX-M10S, but also supports lower-cost generic GNSS modules and now has experimental support for the GT-U16, which offers better reception than some competing receivers at the same or higher price points.
- Wireless firmware updates — Over-the-Ethernet OTA updates let you push new firmware directly from VS Code, with on-screen progress shown on the LCD, so you rarely need to plug in a cable after the first flash.
- Handles more traffic — greatly increased throughput and concurrent request handling, so the server keeps up even on busy networks.
- IPv6 support — the server now responds to NTP requests over IPv6 as well as IPv4.
- Flexible networking — set a custom MAC address or a static IP, instead of always relying on the device's default address and DHCP.
- Optional LCD support — a 4x20 LCD screen was required in v1; in v2 it's entirely optional, along with an optional up time / reset button.
- Optional RGB LED support added — here is how that is used.
- Smart GNSS lock tracking — improved satellite lock and PPS discipline tracking, with results correctly reported as Stratum 16 (undefined) if lock or discipline is temporarily lost, and automatic recovery once it's restored.
- Quick restarts — GPS module identity and baud rate are saved to non-volatile storage, so the server starts up quickly after the initial setup.
- MQTT publishing — optionally publish time server stats to an MQTT broker, with TF card support for queueing far more messages if the broker connection drops.
- Home Assistant integration — ready-made entity and dashboard setup instructions for pulling time server stats into Home Assistant.
- Built-in self-test — an optional startup health check lets the server verify its own core functionality every time it boots.
- Testing tools and guidance — instructions, tools, and links for measuring jitter, drift, RFC 9769 compliance, memory usage, and server performance under stress.
- Updated 3D printed case — enclosure files refreshed for the new WaveShare ESP32-P4-ETH board.
Version 1 described in detail here was built for Arduino / PlatformIO and remains available here.
| Qty | Item |
|---|---|
| 1 | WaveShare ESP32-P4-ETH or ESP32-P4-WIFI6-ETH development board (with or without optional PoE) 1 2 |
| 1 | GPS/GNSS module Recommended: SparkFun GNSS Receiver Breakout - MAX-M10S (Qwiic) 2. Additionally, experimental support for the GT U16 has been added in version 2.9 - this receiver offers superior reception when coupled with the right antenna (see notes below). Additionally, while some lower-cost generic modules UBlox compliant receivers are supported, those without a PPS pin are no longer supported. |
| 1 | GPS/GNSS antenna with SMA connector (SparkFun GPS/GNSS Magnetic Mount Antenna - 3m (SMA)) 2. Alternatively the GT U16 with an L1/L5 antenna offers superior reception. However, in my testing I used the 3M SMA W70C L1/L2/L5 antenna an IPEX to SMA adapter and got very good results (I have the antenna bundled with the GT U16 (link above) on order and when I get it and test it I will update this page). |
| 1 | (Optional) TF Card, formatted as FAT32, if you're using MQTT and want more than four messages queued should broker communications be down) 2 |
| 1 | (Optional) RGB LED: KY-016 (AliExpress) or WS2812 (AliExpress) with a 470Ω resistor and optional lens cap (AliExpress) |
| 1 | (Optional) 4×20 I²C LCD display with HD44780 controller with PCF8574 I²C backpack (AliExpress) 2 |
| 1 | (Optional) Momentary push button for displaying up time and triggering a reset (AliExpress) 2 |
| 1 | (Optional) USB C extension cable (with right angle end) (AliExpress) 2 + two M3*8 |
| — | Miscellaneous: Ethernet cable, female dupont connection wires, small 4" .1" zip ties, solder 2 |
| — | A PoE-capable switch, PoE injector, or USB-C power supply and USB-C cable |
⚠️ WARNING — Do NOT power the ESP32-P4-POE-ETH or the ESP32-P4-WIFI6-POE-ETH via both its USB-C connector and a PoE powered Ethernet cable at the same time. Powering from both simultaneously may damage the ESP32-P4-ETH board, POE switch, or device providing USB power. Either power source alone is sufficient to power the board, GPS module, and LCD screen.
1 See notes in the 'What's New in Version 2' section above.
2 (Optional) there are 3D Printable Case designed for use with ESP32-P4_ETH and these specific components (for more information see below).
(Also Optional) To cut down on wiring a PCB can be used. One designed
to work within this project's 3D printed enclosure is available in the
PCB folder. If your interested, please review the Readme.md file in
that folder for more information.
| GPS pin | ESP32-P4-ETH pin |
|---|---|
| GND | GND |
| VCC | 3V3 |
| PPS | GPIO 20 |
| TXD | GPIO 21 (RX) |
| RXD | GPIO 22 (TX) |
Pro Tip: (Optionally) Adding a capacitor between the GNSS module's Vcc and GND power pins, placed as close as possible to those pins, can improve GNSS power supply stability. Use a 100nf (aka 0.1μf, aka 104) ceramic capacitor, rated at least 50v, such as this one.
Important:
Some GNSS receivers and breakout boards provide a 3.3v PPS (Pulse-Per-Second) output pin while others provide a 5v PPS output pin.
-
Only connect a 3.3v PPS pin to an ESP32 GPIO pin, never connect a 5v PPS pin to an ESP32 GPIO pin
-
For the wire between the ESP32-P4's GPIO pin and the GNSS Receiver's PPS pin use as short and as well connect wire as possible; a standard copper Dupont wire will be fine assuming its gauge is between 24 AWG and 28 AWG
-
The GNSS GND must be connected to the ESP32 GND
Also of note, the above PPS, TX, and RX pin selections were changed in version 2.4 for better forward and backward compatibility between revisions of the ESP32-P4 chip. However, if an earlier version of the software has been working fine for you then you may be able to use the old pin specifications if you want to avoid rewiring your project. Please see the ESP32TimeServerSettings.h file for more information.
| Button | ESP32-P4-ETH pin |
|---|---|
| One terminal | GND |
| The other terminal | GPIO 3 |
| LCD pin | ESP32-P4-ETH pin | |
|---|---|---|
| GND | GND | |
| DI | 470Ω resistor | GPIO 4 |
| 5v | V5 / VBUS |
Important with the WS2812 a 470Ω resistor is require between the DI and GPIO 4 pin
| LCD pin | ESP32-P4-ETH pin |
|---|---|
| GND | GND |
| BLUE | GPIO 4 |
| GREEN | GPIO 5 |
| RED | GPIO 6 |
| LCD pin | ESP32-P4-ETH pin |
|---|---|
| GND | GND |
| VCC | 3V3 |
| SLC/SCL | GPIO 7 |
| SDA | GPIO 8 |
Refer to the pin definitions in
main/ESP32TimeServerSettings.h.
A downloadable 3D printable enclosure designed for use the WaveShare
ESP32-P4-ETH (with or without the POE hat) + Sparkfun GPS boards is included in
the 3D printable case/ folder. Both .stl
(print-ready) and .f3z (Fusion 360 editable source) files are provided so you
can tweak the design to suit your needs.
Note: the 3D printable enclosure files also includes the needed models for version 1 (used an Olimex ESP32-PoE-ISO Rev. B dev board).
To build and flash ESP32TimeServer you will need Espressif's ESP-IDF v6.1 or above. If you don't already have this software installed, just follow the install instructions directly below:
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/index.html
which include these links to get it:
Installation of ESP-IDF and Tools on Windows
Installation of ESP-IDF and Tools on Linux
Installation of ESP-IDF and Tools on macOS
If you want to further develop the project, Visual Studio Code with the ESP-IDF extension is recommended. However, they are not needed to build and flash the project - the instructions for that are below.
Both Visual Studio Code (Community Edition) and the ESP-IDF extension are free to download and use. Here is where you can find more information about them and get your copies:
A kind user of this project has shared more detailed environment setup, build and flash instructions specific to Linux (Debian 13) - these can be found here
The
SparkFun u-blox GNSS Arduino Library v3
(v3.1.14) is included as a git submodule in
3rdparty/SparkFun_u-blox_GNSS_v3. The
local component wrapper in components/SparkFun_u-blox_GNSS_v3 builds it from
that checked-out submodule.
The remaining dependencies are managed automatically via the ESP-IDF Component
Manager (declared in main/idf_component.yml):
esp-idf-lib/hd44780— LCD driveresp-idf-lib/pcf8574— I²C LCD backpack driverespressif/arduino-esp32— Arduino compatibility layer for OTA and serialespressif/mqtt— MQTT client used for optional reporting
Note: The ESP-IDF Component Manager resolves these dependencies dynamically at build time. A
dependencies.lockfile is generated locally on first build but is intentionally not committed because it is git-ignored.
Create a folder for this project, clone this repository into it, and update the SparkFun library within it. For example:
c:mkdir c:\temp\ESP32TimeServerProjectcd \temp\ESP32TimeserverProjectgit clone --recursive https://github.com/roblatour/ESP32TimeServercd ESP32TimeServergit submodule update --init --recursiveAll user-configurable settings — GPIO pins, GPS options, LCD options, button support, time zone, etc. — are centralized in:
main/ESP32TimeServerSettings.h
Edit this file to match your desired hardware setup before building.
Note: The settings file indicates whether certain features are enabled. These include support for an Uptime/Restart Button, a Liquid Crystal Display, MQTT reporting, and Over the Ethernet Updates. Please also note, that if a Liquid Crystal Display is not connected,
LIQUID_CRYSTAL_DISPLAY_ENABLEDmust be0(disabled) or a critical runtime error will occur.
Open a Command Prompt, load the ESP-IDF 6.1 environment, and remove any
existing generated configuration before building. The first build downloads the
Component Manager dependencies into managed_components; this generated folder
is not part of the repository.
Enter the commands below to setup the build environment and build the project:
call C:\esp\v6.1\esp-idf\export.batif exist sdkconfig del sdkconfigYou will need to know your ESP32-P4 chip's revision number to build the program. To determine it connect your ESP32-P4 development board to your computer via a USB cable and enter the following command:
esptool.py -p COMx flash-idNote: Replace
COMxwith your actual serial port. For example: COM6
The output of the above includes chip type and revision number.
Next, use the command below matching your ESP32-P4's chip revision number to build the project:
-
For ESP32-P4 chips at revision 3.1 and above (including the WaveShare ESP32-P4-WIFI6-POE-ETH and newer WaveShare ESP32-P4-ETH development boards):
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;config/esp32p4_rev_v3_1.defaults" set-target esp32p4 build -
For ESP32-P4 chips at revision 3.0:
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;config/esp32p4_rev_v3_0.defaults" set-target esp32p4 build -
For older ESP32-P4 chips with revisions prior to version 3.0 (including older Waveshare ESP32-P4-ETH development boards):
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;config/esp32p4_rev_pre_v3.defaults" set-target esp32p4 build
In the same Command Prompt where ESP-IDF 6.1 was loaded:
idf.py -p COMx flash monitorNote: Replace
COMxwith your actual serial port. For example: COM6Important: Do not use
--forceto flash an image to an earlier revision ESP32-P4.
Following the initial flash, OTE updates can be performed if
OTE_UPDATES_ENABLED was set to 1 (Enabled) for the initial build and flash.
A VS Code task "ESP-IDF: OTE Upload over Ethernet" is included in
.vscode/tasks.json. It builds the firmware and deploys it to the device over
Ethernet using espota.py, so no USB cable is needed after the first flash.
Alternatively, use the terminal command in
UsefulPowerShellCommands.md in the
misc folder.
Please see Setup.md
Once running, and with your Network and System changes setup, you can validate accuracy at https://time.is.
You're also welcome to use this open source software (also developed by me) to stress test your time server:
https://github.com/roblatour/TimeServerStressTest
This project is released under the MIT License — see the LICENSE file for details.
To help support this project, or to just say thanks, you're welcome to 'buy me a coffee'.
Copyright © 2026 Rob Latour

