Index: docs/EXPORT-FORMAT.md
===================================================================
--- docs/EXPORT-FORMAT.md	(revision 7d7b36d4c0b1296801d3425099c25f35c1d12c9d)
+++ docs/EXPORT-FORMAT.md	(revision 624849742199055f381a103d5eb576f6ad16dded)
@@ -99,5 +99,5 @@
 | `summary` | `posts.content_warning` | AS2 summary is the content warning |
 | `sensitive` | `posts.nsfw` | |
-| `published` | `posts.published_at` ?? `created_at` | ISO 8601, UTC. Klonkt stores timestamps in two spellings (`YYYY-MM-DD HH:MM:SS` and full ISO); the archive normalises to ISO. The **instant** survives a round trip, the spelling does not. |
+| `published` | `posts.published_at` ?? `created_at` | ISO 8601, UTC. SQLite writes `CURRENT_TIMESTAMP` in UTC but without a zone, and `Date.parse` reads that shape as *local* time — so a timezone-less value is explicitly read as UTC before conversion. Klonkt stores timestamps in two spellings (`YYYY-MM-DD HH:MM:SS` and full ISO); the archive normalises to ISO. The **instant** survives a round trip, the spelling does not. |
 | `updated` | `posts.updated_at` | omitted when equal to `published` |
 | `url` | `<origin>/<slug>` | the human permalink |
Index: scripts/export-archive.mjs
===================================================================
--- scripts/export-archive.mjs	(revision 7d7b36d4c0b1296801d3425099c25f35c1d12c9d)
+++ scripts/export-archive.mjs	(revision 624849742199055f381a103d5eb576f6ad16dded)
@@ -6,4 +6,13 @@
 //     node scripts/export-archive.mjs <slug> --out boiert.zip
 //     node scripts/export-archive.mjs <slug> --dir ./archief
+//
+// Op een machine met meerdere instances leest hij de .env van die instance zelf
+// (standaard /var/lib/klonkt/<slug>/.env). Dat is niet netjesheid maar noodzaak:
+// zonder die .env opent hij de database die toevallig in de code-map ligt, en op
+// een server die ooit de enkelvoudige opzet draaide is dat een oude lege. Dan
+// komt er een archief uit dat MELDT dat het gelukt is en nul posts bevat.
+//
+//     --data-root <map>   waar de instances staan (standaard /var/lib/klonkt)
+//     --env <pad>         een .env rechtstreeks aanwijzen
 //
 // Het formaat staat in docs/EXPORT-FORMAT.md.
@@ -15,5 +24,5 @@
 import fs from 'fs';
 import path from 'path';
-import { buildArchive, zipArchive, writeArchiveDir } from '../src/services/ArchiveExportService.js';
+import { kiesInstance, eisOrigin } from './instance-env.mjs';
 
 const args = process.argv.slice(2);
@@ -22,11 +31,33 @@
 
 if (!slug) {
-  console.error('gebruik: node scripts/export-archive.mjs <slug> [--dry-run | --out <zip> | --dir <map>]');
+  console.error('gebruik: node scripts/export-archive.mjs <slug> [--data-root <map>] [--env <pad>] [--dry-run | --out <zip> | --dir <map>]');
   process.exit(1);
 }
 
-const uit = buildArchive(slug);
+// EERST de instance kiezen, DAN pas de service laden: src/config/database.js
+// leest DATABASE_PATH bij import en opent de database meteen.
+let gekozen;
+try {
+  gekozen = kiesInstance(slug, {
+    dataRoot: typeof vlag('--data-root') === 'string' ? vlag('--data-root') : null,
+    envPad: typeof vlag('--env') === 'string' ? vlag('--env') : null,
+  });
+} catch (e) { console.error(e.message); process.exit(1); }
+eisOrigin(gekozen.bron);
+
+const { buildArchive, zipArchive, writeArchiveDir } = await import('../src/services/ArchiveExportService.js');
+
+let uit;
+try { uit = buildArchive(slug); }
+catch (e) {
+  console.error(`${e.message}`);
+  if (gekozen.bron) console.error(`(gelezen uit ${gekozen.bron})`);
+  else console.error('(geen instance-.env gevonden -- op een split install: --data-root /var/lib/klonkt)');
+  process.exit(1);
+}
 const t = uit.counts;
-console.log(`site      : ${uit.manifest.site.slug} (${uit.manifest.origin || 'geen PUBLIC_BASE_URL'})`);
+console.log(`site      : ${uit.manifest.site.slug} (${uit.manifest.origin})`);
+console.log(`instellingen uit: ${gekozen.bron || 'de omgeving'}`);
+console.log(`database  : ${process.env.DATABASE_PATH || '(standaard in de code-map)'}`);
 console.log(`posts     : ${t.posts}`);
 console.log(`antwoorden: ${t.replies}   (alleen-lezen archief)`);
Index: scripts/import-archive.mjs
===================================================================
--- scripts/import-archive.mjs	(revision 7d7b36d4c0b1296801d3425099c25f35c1d12c9d)
+++ scripts/import-archive.mjs	(revision 624849742199055f381a103d5eb576f6ad16dded)
@@ -17,5 +17,5 @@
 // en hoort een aparte, bewuste actie te zijn.
 
-import { readArchive, importArchive } from '../src/services/ArchiveImportService.js';
+import { kiesInstance, eisOrigin } from './instance-env.mjs';
 
 const args = process.argv.slice(2);
@@ -24,7 +24,21 @@
 
 if (!slug || !bron) {
-  console.error('gebruik: node scripts/import-archive.mjs <slug> <archief.zip|map> [--dry-run] [--overwrite]');
+  console.error('gebruik: node scripts/import-archive.mjs <slug> <archief.zip|map> [--data-root <map>] [--env <pad>] [--dry-run] [--overwrite]');
   process.exit(1);
 }
+
+// EERST de instance kiezen, DAN pas de service laden -- database.js opent de
+// database bij import. Bij een IMPORT weegt dit zwaarder dan bij een export: in
+// de verkeerde database schrijven is niet terug te draaien.
+const vlag = (naam) => { const i = args.indexOf(naam); return i >= 0 ? (args[i + 1] || null) : null; };
+let gekozen;
+try {
+  gekozen = kiesInstance(slug, { dataRoot: vlag('--data-root'), envPad: vlag('--env') });
+} catch (e) { console.error(e.message); process.exit(1); }
+eisOrigin(gekozen.bron);
+console.log(`instellingen uit: ${gekozen.bron || 'de omgeving'}`);
+console.log(`database        : ${process.env.DATABASE_PATH || '(standaard in de code-map)'}`);
+
+const { readArchive, importArchive } = await import('../src/services/ArchiveImportService.js');
 
 let files;
Index: scripts/instance-env.mjs
===================================================================
--- scripts/instance-env.mjs	(revision 624849742199055f381a103d5eb576f6ad16dded)
+++ scripts/instance-env.mjs	(revision 624849742199055f381a103d5eb576f6ad16dded)
@@ -0,0 +1,95 @@
+//
+// Welke instance bedoel je?
+//
+// Op een split install (deploy/MULTI-INSTANCE.md) deelt elke Klonkt dezelfde code
+// in /opt/klonkt, maar staat zijn DATA onder /var/lib/klonkt/<slug>/ met een eigen
+// .env. Een script dat vanuit de code-map draait zonder die .env te lezen opent
+// dus de VERKEERDE database -- en op een machine die ooit de oude enkelvoudige
+// opzet draaide is dat een achtergebleven storage/database.sqlite in de checkout.
+//
+// Dat ging in de praktijk mis en op de ergste manier: het meldde SUCCES. Een
+// export van een site met zeven jaar aan posten schreef een archief van 444 bytes
+// met "posts: 0", omdat hij in een oude lege database keek. Een leeg archief dat
+// zegt dat het gelukt is, is erger dan een foutmelding.
+//
+// Vandaar dit: los, want de importer en het herstel hebben hem net zo hard nodig.
+
+import fs from 'fs';
+import path from 'path';
+
+/** Lees een .env zonder afhankelijkheden. Alleen KEY=value, # is commentaar. */
+export function leesEnv(pad) {
+  const uit = {};
+  for (const regel of fs.readFileSync(pad, 'utf8').split('\n')) {
+    const s = regel.trim();
+    if (!s || s.startsWith('#')) continue;
+    const i = s.indexOf('=');
+    if (i < 1) continue;
+    let v = s.slice(i + 1).trim();
+    if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
+    uit[s.slice(0, i).trim()] = v;
+  }
+  return uit;
+}
+
+/**
+ * Zet de omgeving voor EEN instance, en zeg hardop welke.
+ *
+ * Moet AANGEROEPEN WORDEN VOORDAT src/config/database.js geladen wordt: die leest
+ * DATABASE_PATH bij import en opent de database meteen. Vandaar dat de scripts
+ * hun service-import uitstellen tot na deze aanroep.
+ *
+ * @returns {{bron: string, env: object}} waar de instellingen vandaan kwamen
+ */
+export function kiesInstance(slug, opts = {}) {
+  const dataRoot = opts.dataRoot || process.env.KLONKT_DATA_ROOT || '/var/lib/klonkt';
+  const kandidaat = opts.envPad || path.join(dataRoot, slug, '.env');
+
+  if (fs.existsSync(kandidaat)) {
+    const env = leesEnv(kandidaat);
+    // De data-map van de instance is de wortel voor relatieve paden in zijn .env,
+    // precies zoals de unit hem draait (WorkingDirectory is de code, DATA_DIR de data).
+    const basis = env.DATA_DIR || path.dirname(kandidaat);
+    const absoluut = (p) => (p && !path.isAbsolute(p) ? path.resolve(basis, p) : p);
+    for (const k of ['DATABASE_PATH', 'MEDIA_PATH', 'AUDIO_PATH', 'AVATAR_PATH', 'COVER_PATH']) {
+      if (env[k]) process.env[k] = absoluut(env[k]);
+    }
+    if (env.PUBLIC_BASE_URL) process.env.PUBLIC_BASE_URL = env.PUBLIC_BASE_URL;
+    if (env.DATA_DIR) process.env.DATA_DIR = env.DATA_DIR;
+    return { bron: kandidaat, env };
+  }
+
+  if (opts.envPad) {
+    // Expliciet meegegeven en niet gevonden: dat is een vergissing en geen reden
+    // om stilletjes iets anders te openen.
+    throw new Error(`geen .env op ${opts.envPad}`);
+  }
+  return { bron: null, env: {} };
+}
+
+/**
+ * Weiger te beginnen als we niet kunnen weten wiens gegevens dit zijn.
+ *
+ * Zonder PUBLIC_BASE_URL komt er een archief uit met een lege `origin`, en dan
+ * weigert de importer de AP-ids te behouden -- hij maakt nieuwe. Daarmee valt
+ * precies de belofte weg waar het formaat om draait: een herstel waarna de
+ * boosts en antwoorden elders hun post terugvinden. Dat mag geen voetnoot in de
+ * uitvoer zijn.
+ */
+export function eisOrigin(bron) {
+  if (process.env.PUBLIC_BASE_URL) return;
+  console.error([
+    'GESTOPT: er is geen PUBLIC_BASE_URL.',
+    '',
+    'Zonder die waarde krijgt het archief een lege origin, en dan maakt een import',
+    'NIEUWE ActivityPub-ids in plaats van de oude te behouden. Boosts, likes en',
+    'antwoorden elders vinden hun post dan niet meer terug -- het archief is dan',
+    'een kopie van de tekst en geen herstel.',
+    '',
+    bron
+      ? `Gelezen uit ${bron}, maar PUBLIC_BASE_URL staat daar niet in.`
+      : 'Geen .env van een instance gevonden. Op een split install:\n'
+        + '  node scripts/export-archive.mjs <slug> --data-root /var/lib/klonkt',
+  ].join('\n'));
+  process.exit(1);
+}
Index: src/services/ArchiveExportService.js
===================================================================
--- src/services/ArchiveExportService.js	(revision 7d7b36d4c0b1296801d3425099c25f35c1d12c9d)
+++ src/services/ArchiveExportService.js	(revision 624849742199055f381a103d5eb576f6ad16dded)
@@ -38,5 +38,23 @@
 
 const sha256 = (buf) => crypto.createHash('sha256').update(buf).digest('hex');
-const toISO = (d) => { const t = Date.parse(d); return isNaN(t) ? null : new Date(t).toISOString(); };
+/**
+ * Naar ISO 8601 in UTC.
+ *
+ * SQLite schrijft CURRENT_TIMESTAMP als "2026-07-01 12:56:10" -- in UTC, maar
+ * ZONDER zone erbij. Date.parse leest die vorm als LOKALE tijd, en dan schuift
+ * elk tijdstempel in het archief mee met de tijdzone van de machine die de export
+ * draait. Op een server in Amsterdam is dat twee uur, en dat merk je pas als je
+ * ergens anders importeert.
+ *
+ * Gevonden doordat Bart vroeg of dit wel naar UTC normaliseert. De testmachine
+ * draait op UTC, dus geen enkele test kon het zien.
+ */
+const toISO = (d) => {
+  if (!d) return null;
+  const s = String(d).trim();
+  const zonderZone = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(s);
+  const t = Date.parse(zonderZone ? `${s.replace(' ', 'T')}Z` : s);
+  return isNaN(t) ? null : new Date(t).toISOString();
+};
 
 const MIME_BY_EXT = {
Index: test/archive-export.test.js
===================================================================
--- test/archive-export.test.js	(revision 7d7b36d4c0b1296801d3425099c25f35c1d12c9d)
+++ test/archive-export.test.js	(revision 624849742199055f381a103d5eb576f6ad16dded)
@@ -175,4 +175,17 @@
 });
 
+test('een SQL-tijdstempel wordt als UTC gelezen, niet als lokale tijd', () => {
+  // SQLite schrijft CURRENT_TIMESTAMP in UTC zonder zone erbij. Date.parse leest
+  // die vorm als LOKALE tijd, en dan schuift elk tijdstempel in het archief mee
+  // met de machine die de export draait -- in Amsterdam twee uur.
+  //
+  // Deze test is alleen zinvol onder een NIET-UTC tijdzone; draai hem daarom ook
+  // eens als:  TZ=Europe/Amsterdam node --test test/archive-export.test.js
+  post('tijdstip', { published_at: '2026-07-01 12:56:10', created_at: '2026-07-01 12:56:10' });
+  const o = lees(AX.buildArchive('me'), 'posts/tijdstip.json');
+  assert.equal(o.published, '2026-07-01T12:56:10.000Z',
+    'het moment uit de database is UTC en hoort dat te blijven');
+});
+
 test('een concept gaat gewoon mee', () => {
   post('concept', { status: 'draft', published_at: null });
