← Home Few Bits
Cover image for Netless NTP: A Minimal Local Time Server for Offline Embedded Gear

Netless NTP: A Minimal Local Time Server for Offline Embedded Gear

Alex Solis Alex Solis ·

What and why

Many embedded projects need the right time: logs, TLS certificate checks, scheduled jobs, and surprisingly bossy cron-replacements. But relying on public NTP servers or a cloud service either leaks metadata or simply isn't available on an air-gapped network. Enter Netless NTP: a small, local NTP/SNTP responder you run on cheap hardware (Raspberry Pi Pico W, ESP32, or similar) that answers time requests on your LAN and gives you a tiny web UI to view and set the clock.

This isn't an RFC-level stratum-0 GPS tower. It's a pragmatic, privacy-first appliance that keeps your devices reasonably accurate and under your control.

What you'll need

  • Microcontroller with Wi‑Fi (Raspberry Pi Pico W, ESP32, or similar)
  • Optional: DS3231 RTC module if you want persistent accuracy across power cycles
  • USB power and a spare port or a small enclosure
  • MicroPython or Arduino toolchain (example below uses MicroPython)

How it works

The core is two simple pieces:

  1. A tiny UDP server that listens on port 123 and responds to SNTP queries with the current time.
  2. A minimal web UI (HTTP) for viewing the current clock, setting time manually, and toggling a periodic sync with an attached RTC if present.

For most LAN clients, a simple SNTP reply is enough: you receive the current Unix timestamp and adjust your clock. If you attach a DS3231, the microcontroller can keep accurate time when re-powered and optionally act as the authoritative source instead of the microcontroller's system clock.

Flash and minimal code (MicroPython)

Below is the essence of a minimal SNTP responder. This is intentionally short — production-quality servers need better error handling, leap-second logic, and security, but this gets devices syncing on your isolated network.

import socket, time, struct s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.bind(('0.0.0.0',123)) while True: data,addr=s.recvfrom(48) if len(data)>=48: t=int(time.time())+2208988800 # convert Unix->NTP epoch tx=struct.pack('!I',t)+struct.pack('!I',0) resp=bytearray(48) resp[0]=0x24 # LI=0, VN=4, Mode=4 (server) resp[40:48]=tx s.sendto(bytes(resp),addr)

A few notes on that snippet:

  • The NTP timestamp is seconds since 1900, so add 2,208,988,800 to Unix time.
  • We set Mode=4 (server). Many embedded clients accept this simple structure.
  • You should run this code at a higher privilege on some platforms or ensure port 123 is reachable; MicroPython allows binding to UDP 123 on most boards.

Adding a tiny web UI

The web UI is just a couple of endpoints: / to view current time and /set to set it from a form or JSON. Using MicroPython's socket module you can serve a handful of bytes of HTML and accept a POST. Make the page minimal — just a timestamp, a button to sync to the RTC, and a manual field to set Unix time. This keeps the UI tiny and privacy-respecting.

Optional: attach a DS3231 RTC

If your device loses power often, add a DS3231 on I2C. On boot, read RTC and set the system clock. Periodically write back the system time to the RTC so the hardware clock stays accurate. Make RTC writes deliberate (every few minutes) to avoid wasting writes and simplify debugging.

Discovery and DHCP

For convenience, give the appliance a predictable local IP via your router's DHCP reservations or use mDNS so clients can find netless.local. Devices often hardcode NTP server names — provide instructions to point them to your new local address. If you control device provisioning, push the local IP as the NTP server.

Troubleshooting & tips

  • Firewall: ensure UDP port 123 is open between clients and the server. On many home routers, LAN-to-LAN traffic is allowed by default but double-check guest networks.
  • Permissions: on some devices binding UDP port 123 may require root-like privileges. If binding fails, try a nonstandard port for testing (e.g., 12345) and point a test client explicitly at that port.
  • Stratum: this minimal server reports a low stratum; if clients demand stricter stratum rules, consider configuring them to accept your server as authoritative or use the DS3231 path to keep stability high.
  • Accuracy: without an external timebase, the microcontroller's clock will drift. DS3231 reduces that problem dramatically.
  • Security: NTP is unauthenticated by default. On a local, trusted network this is usually fine. If you need authentication, run a more complete NTP daemon on a more capable platform and use symmetric keys.

When not to use Netless NTP

If you need sub-millisecond accuracy, GPS-disciplined time, or public time service for many thousands of clients, step up to a proper NTP server on a full OS. This project is meant for makers, home labs, and embedded fleets that need reliable local time without contacting the outside world.

Wrap-up

Netless NTP is a tiny, low-cost way to put time back in your control. It's quick to set up, privacy-friendly, and useful for keeping logs sensible and TLS checks behaving on air-gapped or privacy-conscious networks. You can iterate: start with the minimal SNTP responder above, add a tiny web UI, then attach a DS3231 RTC when you want persistence. If you enjoy small, practical tools that just work without the cloud, this one fits the Few Bits philosophy: tiny footprint, clear purpose, and pleasantly offline.