Linux vs. Windows Timezone: TZ vs. WEBSITE_TIME_ZONE Mismatch¶
Status: Published
Experiment completed with real data on 2026-05-03.
1. Question¶
On a Linux App Service, what is the correct way to set the process timezone? Does WEBSITE_TIME_ZONE (the Windows App Service setting) affect a Linux app? And what happens when a Windows-style timezone name (e.g., "Korea Standard Time") is used on a Linux runtime instead of the IANA name ("Asia/Seoul")?
2. Why this matters¶
App Service runs on both Windows and Linux. The timezone configuration mechanism differs between the two:
- Windows:
WEBSITE_TIME_ZONEaccepts Windows timezone names (e.g.,"Korea Standard Time"). - Linux:
TZenv var accepts IANA timezone names (e.g.,"Asia/Seoul").WEBSITE_TIME_ZONEis silently ignored.
Teams migrating from Windows to Linux App Service, or copying app settings between environments, often carry over WEBSITE_TIME_ZONE with a Windows-style name. The result is that datetime.now() (Python), new Date() (Node.js), and similar calls return UTC unexpectedly — causing timestamp bugs in logs, scheduled jobs, and any time-sensitive business logic — with no error at the platform level.
3. Customer symptom¶
"Timestamps in our logs are 9 hours behind even though we set the timezone" or "The scheduled job runs at the wrong time after migrating to Linux" or "We set WEBSITE_TIME_ZONE but datetime.now() still returns UTC."
4. Hypothesis¶
- H1: On Linux App Service,
TZ=Asia/Seoul(IANA name) correctly shiftsdatetime.datetime.now()to KST (+09:00). ✅ Confirmed - H2: On Linux App Service,
TZ="Korea Standard Time"(Windows timezone name) causes a PythonZoneInfolookup failure;datetime.datetime.now()returns UTC (the system default). ✅ Confirmed - H3: On Linux App Service,
WEBSITE_TIME_ZONE="Korea Standard Time"is silently ignored by the OS — it does not affect the process timezone. ✅ Confirmed - H4: The platform does not warn or error when
WEBSITE_TIME_ZONEis set on a Linux app; the setting is accepted without effect. ✅ Confirmed
5. Environment¶
| Parameter | Value |
|---|---|
| Service | Azure App Service |
| SKU / Plan | B1 |
| Region | Korea Central |
| Runtime | Python 3.11 |
| OS | Linux |
| Date tested | 2026-05-03 |
6. Variables¶
Experiment type: Configuration / Runtime behavior
Controlled:
- Single Linux App Service (B1, Python 3.11) with a Flask endpoint returning
datetime.datetime.now()and timezone diagnostic info - App settings changed between scenarios; app restarted between each test
Observed:
datetime_now_no_tz: value ofdatetime.datetime.now().isoformat()(no explicit tz — affected byTZenv)local: value ofdatetime.now(utc).astimezone(configured_tz).isoformat()tz_valid: whether the configured timezone name was recognized by Pythonzoneinfo
Scenarios:
| Scenario | TZ setting |
WEBSITE_TIME_ZONE |
Expected |
|---|---|---|---|
| Baseline | (not set) | (not set) | UTC |
| S1 | Asia/Seoul |
(not set) | KST +09:00 |
| S2 | Korea Standard Time |
(not set) | Error / UTC fallback |
| S3 | (empty) | Korea Standard Time |
UTC (ignored) |
| S4 | Asia/Seoul |
(empty) | KST +09:00 (confirmed fix) |
7. Instrumentation¶
- Flask endpoint at
/returning JSON withutc,local,TZ_env,WEBSITE_TIME_ZONE_env,tz_valid,datetime_now_no_tz curl -s https://<app>.azurewebsites.net/az webapp config appsettings setto change settings between scenarios
8. Procedure¶
- Deployed a minimal Flask app (Python 3.11, Linux, B1) returning timezone diagnostics via
zoneinfoanddatetime. - Baseline: No
TZorWEBSITE_TIME_ZONEset → measured UTC output. - S1: Set
TZ=Asia/Seoul→ waited for restart (~30s) → measured KST output. - S2: Set
TZ="Korea Standard Time"(Windows style) → waited for restart → measured behavior. - S3: Set
TZ="",WEBSITE_TIME_ZONE="Korea Standard Time"→ waited for restart → measured behavior. - S4: Reset to
TZ=Asia/Seoul→ confirmed correct behavior restored.
9. Expected signal¶
- S1:
datetime_now_no_tzshows KST (21:xx) not UTC (12:xx).tz_valid: true. - S2:
tz_valid: false, error message inlocalfield,datetime_now_no_tzshows UTC. - S3:
WEBSITE_TIME_ZONEignored;datetime_now_no_tzshows UTC.tz_valid: false(empty TZ).
10. Results¶
| Scenario | datetime_now_no_tz |
tz_valid |
local |
|---|---|---|---|
| Baseline | 2026-05-03T12:32:33 (UTC) |
true | +00:00 |
S1: TZ=Asia/Seoul |
2026-05-03T21:34:39 (KST) |
true | +09:00 |
S2: TZ=Korea Standard Time |
2026-05-03T12:36:29 (UTC) |
false | ERROR: 'No time zone found with key Korea Standard Time' |
S3: WEBSITE_TIME_ZONE=Korea Standard Time |
2026-05-03T12:38:23 (UTC) |
false | ERROR: ZoneInfo keys must be normalized... |
S4: TZ=Asia/Seoul (reset) |
2026-05-03T21:40:15 (KST) |
true | +09:00 |
Raw responses (selected):
Baseline:
```json {"TZ_env":"NOT_SET","WEBSITE_TIME_ZONE_env":"NOT_SET","datetime_now_no_tz":"2026-05-03T12:32:33.110096","local":"2026-05-03T12:32:33.110038+00:00","tz_valid":true,"utc":"2026-05-03T12:32:33.110038+00:00"}
``text
**S1 —TZ=Asia/Seoul`:**
```json {"TZ_env":"Asia/Seoul","WEBSITE_TIME_ZONE_env":"NOT_SET","datetime_now_no_tz":"2026-05-03T21:34:39.352547","local":"2026-05-03T21:34:39.352205+09:00","tz_valid":true,"utc":"2026-05-03T12:34:39.352205+00:00"}
``text
**S2 —TZ=Korea Standard Time` (Windows name on Linux):**
```json {"TZ_env":"Korea Standard Time","WEBSITE_TIME_ZONE_env":"NOT_SET","datetime_now_no_tz":"2026-05-03T12:36:29.247750","local":"ERROR: 'No time zone found with key Korea Standard Time'","tz_valid":false,"utc":"2026-05-03T12:36:29.245328+00:00"}
``text
**S3 —WEBSITE_TIME_ZONE=Korea Standard Time` (TZ empty):**
```json {"TZ_env":"","WEBSITE_TIME_ZONE_env":"Korea Standard Time","datetime_now_no_tz":"2026-05-03T12:38:23.398819","local":"ERROR: ZoneInfo keys must be normalized relative paths, got: ","tz_valid":false,"utc":"2026-05-03T12:38:23.398755+00:00"}
```text
11. Interpretation¶
Observed: TZ=Asia/Seoul (IANA name) correctly shifts datetime.datetime.now() to KST (+09:00) on Linux App Service.
Observed: TZ="Korea Standard Time" is silently passed to the Linux process as an environment variable. Python zoneinfo raises a key lookup error. datetime.datetime.now() falls back to UTC — the system default — without raising any exception at the platform or application level.
Observed: WEBSITE_TIME_ZONE is stored as an env var in the process (visible via os.environ.get), but the Linux OS timezone resolution mechanism (/etc/localtime, TZ env) does not consult it. It has zero effect on runtime timezone behavior.
Inferred: The App Service control plane accepts WEBSITE_TIME_ZONE on Linux apps without validation. No warning is emitted. A customer setting WEBSITE_TIME_ZONE on a Linux app with the expectation it works (as it does on Windows) will see silent UTC timestamps — the worst kind of bug.
12. What this proves¶
- Proven:
TZwith an IANA timezone name is the only correct mechanism for setting process timezone on Linux App Service Python runtimes. - Proven:
WEBSITE_TIME_ZONEis silently ignored by the Linux runtime environment. - Proven: Windows timezone names (
"Korea Standard Time") are rejected by Pythonzoneinfowith aZoneInfoNotFoundError. No platform-level error or warning is produced. - Proven: The failure is silent —
datetime.datetime.now()returns UTC without throwing, making this bug difficult to detect without explicit timezone logging.
13. What this does NOT prove¶
- Behavior of other runtimes (Node.js, Java, .NET) on Linux under the same
TZmisconfiguration — not tested. - Whether
WEBSITE_TIME_ZONEaffects OS-level cron or system processes on Linux App Service (only Python process behavior tested). - Behavior of
pytzorpython-dateutilunder Windows timezone names —zoneinfo(stdlib, Python 3.9+) was used exclusively.
14. Support takeaway¶
When a Linux App Service customer reports timestamps appearing in UTC despite timezone configuration:
-
Check
TZenv var (notWEBSITE_TIME_ZONE):bash az webapp config appsettings list -n <app> -g <rg> --query "[?name=='TZ'].value" -o tsv -
Verify the value is an IANA name, not a Windows name:
- ✅ Correct:
Asia/Seoul,America/New_York,Europe/London,UTC - ❌ Wrong:
Korea Standard Time,Eastern Standard Time,Pacific Standard Time - If
WEBSITE_TIME_ZONEis set butTZis missing or wrong — addTZwith the IANA equivalent. Use the Unicode CLDR mapping to convert Windows → IANA names. - Restart the app after changing
TZ.
15. Reproduction notes¶
```bash
Create test environment¶
az group create -n rg-tz-test -l koreacentral
az appservice plan create -n plan-tz-test -g rg-tz-test --sku B1 --is-linux
az webapp create -n
Correct: IANA timezone via TZ¶
az webapp config appsettings set -n
Wrong: Windows name on Linux (silently fails)¶
az webapp config appsettings set -n
Wrong: Windows-only setting (silently ignored on Linux)¶
az webapp config appsettings set -n
```text Flask diagnostic endpoint used in this experiment:
```python import os, datetime, zoneinfo from flask import Flask, jsonify
app = Flask(name)
@app.route("/") def index(): now_utc = datetime.datetime.now(datetime.timezone.utc) tz_env = os.environ.get("TZ", "NOT_SET") try: local_tz = zoneinfo.ZoneInfo(tz_env) if tz_env not in ("NOT_SET", "") else datetime.timezone.utc local_str = now_utc.astimezone(local_tz).isoformat() tz_valid = True except Exception as e: local_str = f"ERROR: {e}" tz_valid = False return jsonify({ "utc": now_utc.isoformat(), "local": local_str, "TZ_env": tz_env, "WEBSITE_TIME_ZONE_env": os.environ.get("WEBSITE_TIME_ZONE", "NOT_SET"), "tz_valid": tz_valid, "datetime_now_no_tz": datetime.datetime.now().isoformat() })
```text