LinuxでNTPを使用せずに時刻をあわせる

背景

大学のイカれたネットワークでは、NTPが使用できない(UDP123)。そのためデバイスの時刻がじんわりズレていく。普段持ち歩いてるデバイスであれば、他のネットワークに接続したりするのであんまり困らないんだけど、大学で常時稼働させる場合は色々めんどくさいことが起きる。(時計を表示しているアプリだとズレて表示される / TLSが死ぬのでインターネットが使えなくなるなど)

やり方(GNU/Linux)

sudo date -s "$(curl -sI http://www.google.com | sed -n 's/^date: //Ip' | tr -d '\r')"

解説

HTTPは接続されるので、HTTPレスポンスのDateヘッダを読む。

$ curl -sI http://www.google.com | grep -i '^date:'
date: Mon, 24 Aug 2026 03:12:44 GMT

RFCで、サーバはレスポンス生成時刻を Date ヘッダに入れることになっている。これを使って時刻を合わせればだいたい合う。(多分)

http:// でアクセスしている理由もあって、https:// だと、時刻が大幅にズレているとTLSの関係でうまく取得できない場合がある。

平文HTTPなので改ざん可能であるが、まあそのへんは技術者倫理が徹底された大学なのでなにも問題がない。

応用(NixOSでの実装)

{ config, lib, pkgs, ... }:
{
  # ---- 通常の NTP ----
  services.timesyncd = {
    enable = true;
    servers = [ "0.nixos.pool.ntp.org" "1.nixos.pool.ntp.org" ];
  };

  # ---- HTTP フォールバック ----
  systemd.services.http-timesync = {
    description = "HTTP-based time sync (fallback for blocked NTP)";
    after = [ "network-online.target" ];
    wants = [ "network-online.target" ];
    serviceConfig.Type = "oneshot";
    script = ''
      # NTP が既に同期済みなら何もしない
      if timedatectl show | grep -q "NTPSynchronized=yes"; then
        echo "NTP already synchronized, skipping HTTP fallback"
        exit 0
      fi

      echo "NTP not synchronized, trying HTTP fallback..."

      fetch_date() {
        ${pkgs.curl}/bin/curl -sI --max-time 5 "$1" 2>/dev/null \
          | grep -i '^date:' | sed 's/^[Dd]ate: //' | tr -d '\r' || true
      }

      datetime=$(fetch_date http://www.google.com)
      if [ -z "$datetime" ]; then
        echo "Google failed, trying Cloudflare..."
        datetime=$(fetch_date http://www.cloudflare.com)
      fi

      if [ -n "$datetime" ]; then
        echo "Setting time from HTTP: $datetime"
        ${pkgs.coreutils}/bin/date -s "$datetime"
      else
        echo "All HTTP time sources failed"
        exit 1
      fi
    '';
  };

  systemd.timers.http-timesync = {
    description = "Timer for HTTP time sync fallback";
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnBootSec = "30s";      # 起動 30 秒後
      OnUnitActiveSec = "5m"; # 以降 5 分ごと
    };
  };
}

コメント

タイトルとURLをコピーしました