संचालन गाइड Hetzner · netcup · STRATO · 2 नवंबर 2026 तक

कई प्रदाताओं में अत्यधिक उपलब्ध nextxch

जब कोई पूरा प्रदाता ऑफ़लाइन हो जाए, तब भी मेल चलती रहनी चाहिए: महँगे anycast के बिना, एंटरप्राइज़ लोड बैलेंसर के बिना, Git से डिप्लॉय करने योग्य। यह गाइड Hetzner, netcup और STRATO के कम लागत वाले सर्वरों पर तीन सेटअप दिखाती है, सेकंड-दर-सेकंड बताती है कि आउटेज के दौरान क्या होता है, और संसाधनों तथा लागत की गणना करती है। प्रदाता के हर दावे के साथ जाँच की तिथि सहित स्रोत का लिंक है, nextxch के हर दावे के साथ कोड में स्रोत फ़ाइल का नाम दिया गया है।

01 · उच्च उपलब्धता के लिए nextxch क्या प्रदान करता है (कोड में जाँचा गया)

nextxch active-active सक्षम है: हर नोड SMTP, IMAP, JMAP, ActiveSync और HTTPS स्वीकार करता है, कोई निष्क्रिय नोड और कोई स्टिकी सत्र नहीं है। PostgreSQL साझा स्थिति रखता है, संदेश स्वयं ब्लॉब स्टोर में SHA-256-एड्रेस्ड .eml ऑब्जेक्ट के रूप में संग्रहीत होते हैं।

क्षमताकैसेकोड में स्रोत
Redis के बिना क्लस्टर बैकप्लेननए मेल, फ़्लैग, expunge, कतार जैसे ईवेंट के लिए PostgreSQL LISTEN/NOTIFY (चैनल nextxch_cluster_events); UNLOGGED टेबल cluster_ephemeral और cluster_nodes; हर 15 s पर हार्टबीट, 60 s तक हार्टबीट न मिलने पर नोड हटा दिए जाते हैंsrc/Nextxch.Storage/Cluster/PostgresClusterCoordinator.cs
लीडर कार्यpg_try_advisory_lock: ठीक एक नोड ACME प्रमाणपत्र नवीनीकृत करता है, लाइसेंस हार्टबीट भेजता है, LDAP सिंक करता है, कैलेंडर रिमाइंडर भेजता हैAcmeCertificateManager.cs, ClusterLicenseService.cs, LdapSyncService.cs, CalendarReminderPushService.cs
नोड्स के पार SMTP कतारFOR UPDATE SKIP LOCKED: हर नोड अलग कतार आइटम लेता है, कोई दोहरी डिलीवरी नहींsrc/Nextxch.Storage/Postgres/PostgresMailStore.cs
क्लस्टर-सक्षम डेटाबेसPostgreSQL (क्लस्टर के लिए डिफ़ॉल्ट) और SQL Server (sp_getapplock); SQLite केवल सिंगल नोडsrc/Nextxch.Storage/StorageServiceCollectionExtensions.cs
ब्लॉब स्टोरFileSystem, S3 (Content-MD5 के साथ अपना SigV4 क्लाइंट) या Hybrid (लोकल NVMe कैश + S3)। Object Lock मोड Normal, Governance, Compliance और legal holdsrc/Nextxch.Storage/Blobs/S3BlobStore.cs, HybridBlobStore.cs
प्रमाणपत्र और DKIMACME प्रमाणपत्र और DKIM कुंजियाँ डेटाबेस में रहती हैं; HTTP-01 चैलेंज पूरे क्लस्टर में साझा होते हैं, ताकि कोई भी नोड उत्तर दे सके; नवीनीकरण पर लीफ़ कुंजी बनी रहती है (DANE 3 1 1 मान्य रहता है)src/Nextxch.Security/Tls/AcmeCertificateManager.cs
हेल्थ एंडपॉइंट/health/live (DB नहीं), /health/ready (DB पहुँच योग्य, अन्यथा 503), /health (क्लस्टर पीयर सहित अवलोकन)src/Nextxch.Server/Program.cs
वास्तविक क्लाइंट IPSMTP, IMAP, ManageSieve के लिए PROXY protocol v1/v2 (Cluster:EnableProxyProtocol) और TrustedProxyCidrs से HTTP के लिए X-Forwarded-Forsrc/Nextxch.Core/Abstractions/IClusterCoordinator.cs
आर्टिफ़ैक्ट परिनियोजित करेंdocker-compose.yml, Kubernetes मैनिफ़ेस्ट (3 रेप्लिका, HPA 2 से 10, PDB, probes), Helm chart, deb/rpm, Windows MSIdeploy/kubernetes/, deploy/helm/nextxch/, deploy/packages/
लाइसेंससक्रिय मेलबॉक्स गिने जाते हैं, नोड या पॉड नहीं; लाइसेंस की स्थिति cluster_kv के माध्यम से साझा की जाती हैsrc/Nextxch.Server/Services/ClusterLicenseService.cs, मूल्य

प्रति नोड पोर्ट: 25, 465, 587 (SMTP), 143, 993 (IMAP), 4190 (ManageSieve), 80/443 (HTTPS, ACME, वेबमेल, ActiveSync, JMAP)। POP3 110/995 डिफ़ॉल्ट रूप से बंद है (Pop3Settings.cs में Pop3:Enabled=false)।

एकमात्र अनिवार्य आवश्यकता
सभी नोड को किसी भी समय ठीक एक लिखने योग्य PostgreSQL प्राइमरी और एक साझा ब्लॉब स्टोर की आवश्यकता होती है। पहुँच योग्य डेटाबेस के बिना /health/ready 503 लौटाता है और नोड प्राप्तकर्ताओं को सत्यापित नहीं कर सकता। इसलिए प्रदाताओं के पार उच्च उपलब्धता का अर्थ है PostgreSQL और ब्लॉब स्टोर को साइटों के पार रेप्लिकेट करना और स्वचालित रूप से स्विच करना। तीनों सेटअप यही बनाते हैं।

02 · मेल-विशिष्ट नियम जो उच्च उपलब्धता को आसान बनाते हैं

इनबाउंड: MX में फेलओवर अंतर्निहित है

प्रेषक MX रिकॉर्ड को प्राथमिकता के अनुसार क्रमबद्ध करते हैं और उन्हें हर पते को आज़माना होता है; समान प्राथमिकता होने पर उन्हें क्रम को अनिवार्य रूप से यादृच्छिक करना होता है। [1] यदि कोई लक्ष्य पहुँच से बाहर है, तो पुनः प्रयास अंतराल कम से कम 30 मिनट और छोड़ने का समय कम से कम 4 से 5 दिन होना चाहिए। [1]

आउटबाउंड: प्रति IP SPF और PTR

SPF में हर साइट का हर आउटबाउंड IP सूचीबद्ध होना चाहिए; ip4/ip6, 10 DNS लुकअप की सीमा में नहीं गिने जाते। [2] Gmail को मान्य फ़ॉरवर्ड और रिवर्स DNS चाहिए; PTR प्रत्येक प्रदाता पर अलग से सेट किया जाता है। [8][14][27][31]

DKIM, MTA-STS, DANE

DKIM कुंजियाँ क्लस्टर डेटाबेस में रहती हैं, एक DNS सेट सभी नोड के लिए काम करता है; सेलेक्टर कई कुंजियों को समानांतर रूप से अनुमति देते हैं। [3] MTA-STS नीति में हर MX होस्ट का नाम होना चाहिए; वाइल्डकार्ड केवल सबसे बाएँ लेबल को कवर करते हैं। [4] DANE को प्रति MX होस्ट एक TLSA रिकॉर्ड चाहिए, 3 1 1 अनुशंसित है, और एक DNSSEC-हस्ताक्षरित ज़ोन। [5]

क्लाइंट: IMAP, सबमिशन, HTTPS

मेल क्लाइंट में MX जैसा फेलओवर नहीं होता। वे एक नाम (यहाँ mail.example.com) से कनेक्ट होते हैं, जिसके A रिकॉर्ड एक हेल्थ-चेक जॉब द्वारा बनाए रखे जाते हैं। IDLE कनेक्शन कम से कम हर 29 मिनट में फिर से जारी होते हैं और तब किसी स्वस्थ नोड पर पहुँचते हैं। [7][6]

दो साइटों के लिए DNS ज़ोन (टेम्पलेट)
; Zone example.com (TTL in seconds). IPs are documentation ranges: replace them.
; Site A = Hetzner 192.0.2.10, site B = netcup 198.51.100.20, site C (medium setup) = 203.0.113.30

; Inbound SMTP: equal preference, senders MUST randomize (RFC 5321 section 5.1).
example.com.            3600 IN MX 10 mx1.example.com.
example.com.            3600 IN MX 10 mx2.example.com.

; Static per-site names: never touched by the failover job (stable FCrDNS for PTR).
mx1.example.com.        3600 IN A     192.0.2.10
mx2.example.com.        3600 IN A     198.51.100.20

; Client name (IMAP, submission, HTTPS, webmail, ActiveSync, autodiscover):
; short TTL, kept up to date by the health-check job. Start with all healthy nodes.
mail.example.com.         60 IN A     192.0.2.10
mail.example.com.         60 IN A     198.51.100.20

; SPF: every outbound IP of every site. ip4/ip6 do not count against the 10-lookup limit (RFC 7208 4.6.4).
example.com.            3600 IN TXT   "v=spf1 ip4:192.0.2.10 ip4:198.51.100.20 ip4:203.0.113.30 -all"

; DKIM: nextxch keeps the keys in the cluster database, so one record set serves all nodes.
; Publish the selectors shown under /admin/ (Domains > DKIM).

; MTA-STS: the policy must list every MX host (see the note on gaps).
_mta-sts.example.com.   3600 IN TXT   "v=STSv1; id=20261102"

; DANE (optional, needs a DNSSEC-signed zone): one TLSA record per MX host.
; nextxch reuses the leaf key on renewal, so "3 1 1" stays stable.
_25._tcp.mx1.example.com. 3600 IN TLSA 3 1 1 <sha256-of-spki>
_25._tcp.mx2.example.com. 3600 IN TLSA 3 1 1 <sha256-of-spki>

; PTR (set in the provider console, not in this zone):
;   192.0.2.10    -> mx1.example.com   (Hetzner Console / Robot)
;   198.51.100.20 -> mx2.example.com   (netcup SCP)
EHLO नाम और PTR
सर्वर होस्टनाम (mail.example.com, क्लाइंट, autodiscover और प्रमाणपत्र के लिए) पूरे क्लस्टर पर लागू होता है। प्रत्येक नोड अपना EHLO/HELO नाम स्वयं सेट करता है: कॉन्फ़िग में Smtp:HeloHostname या /admin/ में "Hostname, IPv6 & Ports" के अंतर्गत (प्रति नोड EHLO नाम, प्रति नोड PTR जाँच के साथ)। इसका उपयोग 220 बैनर, EHLO उत्तर और आउटबाउंड EHLO के लिए होता है। प्रत्येक IP का PTR उसके निश्चित नाम mx1/mx2 पर इंगित करें; DNS एडवाइज़र प्रति नोड एक HELO SPF रिकॉर्ड दिखाता है। RFC 5321 सेक्शन 4.1.4 वैसे भी किसी संदेश को केवल मेल न खाते EHLO नाम के कारण अस्वीकार करने से मना करता है। [1][8]
प्रति नोड EHLO नाम
# appsettings.Production.json per node (or admin: Hostname, IPv6 & Ports > EHLO name per node)
# Site A (Hetzner):
{ "Smtp": { "ServerHostName": "mail.example.com", "HeloHostname": "mx1.example.com" } }
# Site B (netcup):
{ "Smtp": { "ServerHostName": "mail.example.com", "HeloHostname": "mx2.example.com" } }
# PTR 192.0.2.10 -> mx1.example.com, PTR 198.51.100.20 -> mx2.example.com
सभी MX होस्ट के साथ MTA-STS नीति (nextxch द्वारा प्रति डोमेन प्रदान की जाती है)
# Served by nextxch itself at https://mta-sts.example.com/.well-known/mta-sts.txt
# (admin: Domains > domain > Transport > MTA-STS). Empty MX list = automatic:
# the domain's MX records from DNS + server host name + EHLO names of all nodes.
# Wildcards like *.example.com are allowed. Every change changes the policy id;
# the DNS advisor shows the matching _mta-sts TXT record.
version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 604800

03 · क्लाइंट कैसे पुनर्निर्देशित होते हैं: विकल्पों की तुलना

प्रदाता के फेलओवर IP केवल एक प्रदाता के भीतर मदद करते हैं। पूरे प्रदाता के आउटेज के लिए DNS सरल और सस्ता उपकरण बना रहता है।

विकल्पदायरालागतआकलन
समान प्राथमिकता वाले कई MXप्रदाताओं के पार0 €इनबाउंड मेल के लिए अनिवार्य, मानकीकृत [1]
अपना हेल्थ-चेक जॉब + DNS API (Cloudflare, Hetzner, deSEC, netcup)प्रदाताओं के पार0 €अनुशंसित। Cloudflare: TTL 60 s से (Enterprise 30 s), auto = 300 s। Hetzner DNS: निःशुल्क, TTL 60 s से, Hetzner Console में API। deSEC: प्रति डोमेन न्यूनतम TTL, उदाहरण 3600 s, कम केवल अनुरोध पर। netcup: CCP DNS API मौजूद है, न्यूनतम TTL प्रलेखित नहीं। [38][39][21][40][29]
Cloudflare Load Balancing (केवल DNS)प्रदाताओं के पारUSD 5/माह सेEnterprise के बाहर हर 15 s पर हेल्थ चेक (TCP, SMTP सहित); A/AAAA/CNAME के लिए, MX/SRV केवल Enterprise में। mail.example.com के लिए अपने जॉब की जगह लेता है। [33][34][35][36]
Cloudflare Spectrumप्रदाताओं के पारEnterpriseमनमाने TCP पोर्ट (25, 465, 587, 993) केवल Enterprise ऐड-ऑन के रूप में; Pro/Business में केवल SSH, Minecraft, RDP। [37]
Hetzner Floating IP (Cloud)एक प्रोजेक्ट, एक नेटवर्क ज़ोनIPv4 €3.00, IPv6 €1.00/माहकेवल Hetzner के भीतर [9]
Hetzner फेलओवर IP (Robot, डेडिकेटेड)Hetzner डेटा सेंटर€4.20/माह + €4.90 सेटअपRobot API से 90 से 110 s में स्विच, केवल Hetzner के अंदर [11]
netcup फेलओवर IPवही netcup स्थान€5.00/माह, VAT सहितSCP में कभी भी निःशुल्क पुनः रूट करें, केवल netcup के भीतर [24][25]
Akamai/Linode IP शेयरिंगएक डेटा सेंटर, दो LinodeUSD 5 से (Nanode)BGP-आधारित (lelastic), केवल उसी डेटा सेंटर में; NodeBalancer Basic USD 10, TCP/HTTP [41][42]
Anycast / अपना BGPglobalउच्चअपना ASN (सदस्यता या प्रायोजक LIR के माध्यम से RIPE अनुबंध) और अपना पता स्थान (Vultr पर कम से कम /24 IPv4 या /48 IPv6)। अधिकांश nextxch इंस्टॉलेशन के लिए अनुपातहीन। [43][44]
विटनेस की आवश्यकता क्यों है
दो साइटों के साथ कोई भी साइट यह नहीं बता सकती कि दूसरी विफल हुई है या केवल कनेक्शन टूटा है। यदि दोनों स्वयं को प्रमोट कर लें, तो दो अलग-अलग होते डेटाबेस बनेंगे (split brain)। etcd बहुमत से निर्णय लेता है: तीन में से दो सदस्य पर्याप्त हैं। इसलिए: तीन प्रदाताओं पर तीन वोट, जिनमें तीसरा एक छोटा witness हो सकता है। [48][53]

04 · सेटअप S: दो साइटें और एक विटनेस

छोटी और मध्यम कंपनियों के लिए। साइट A (उदा. Hetzner Cloud) और साइट B (उदा. netcup VPS) दोनों पर nextxch, Patroni के अंतर्गत PostgreSQL, एक etcd सदस्य और एक Garage ज़ोन चलते हैं। किसी तीसरे प्रदाता (उदा. STRATO) पर एक छोटा विटनेस C तीसरा etcd सदस्य, तीसरी Garage प्रति और DNS फेलओवर चेकर रखता है। तीनों WireGuard से आपस में जुड़े (मेश) हैं।

flowchart TB
  U["Users: IMAP, SMTP submission, HTTPS"] -->|"mail.example.com TTL 60"| DNS["DNS with health-check job"]
  S["Remote mail servers"] -->|"MX 10 mx1 / MX 10 mx2"| A1
  S --> B1
  DNS --> A1
  DNS --> B1
  subgraph A["Site A: Hetzner"]
    A1["nextxch"] --> AP[("PostgreSQL primary")]
    A1 --> AG["Garage zone A"]
    AE["etcd"]
  end
  subgraph B["Site B: netcup"]
    B1["nextxch"] --> BP[("PostgreSQL replica")]
    B1 --> BG["Garage zone B"]
    BE["etcd"]
  end
  subgraph C["Witness C: STRATO"]
    CE["etcd"]
    CG["Garage zone C"]
    CF["Failover checker"]
  end
  B1 -.->|"WireGuard"| AP
  AP ==>|"synchronous replication"| BP
  AG <-.-> BG
  BG <-.-> CG
  AE <-.-> BE
  BE <-.-> CE
आर्किटेक्चर सेटअप S

blob स्टोर के रूप में Garage क्यों?

एक ही प्रदाता पर S3 बकेट उस प्रदाता के साथ ही चला जाएगा, और nextxch वर्तमान में केवल एक S3 लक्ष्य पर लिख सकता है। Garage एक S3-संगत स्टोर (AGPL-3.0) है, जो इंटरनेट पर कई डेटा सेंटरों के लिए बनाया गया है (प्रोजेक्ट आवश्यकता: अधिकतम 200 ms लेटेंसी, कम से कम 50 Mbit/s)। replication_factor = 3 के साथ प्रति ज़ोन एक प्रति होती है; जब तक केवल एक ज़ोन विफल होता है, पढ़ना और लिखना जारी रहता है (राइट कोरम 2)। केवल दो ज़ोन होने पर एक ज़ोन खोने से स्टोर केवल-पठन रह जाएगा, इसीलिए विटनेस भी एक पूर्ण प्रति रखता है। [60][61]

परीक्षित नहीं और Object Lock के बिना
Garage न तो S3 Object Lock का समर्थन करता है और न ही वर्ज़निंग का। WORM प्रतिधारण (governance/compliance) वाले डोमेन के कारण nextxch प्रतिधारण हेडर भेजता है और इसलिए उन्हें Object Lock वाले S3 की आवश्यकता होती है; GoBD कॉपी के लिए अध्याय 09 देखें। इस गाइड के लिए Garage पर nextxch का लोड-परीक्षण नहीं किया गया: प्रोडक्शन से पहले परीक्षण डेटा से सत्यापित करें। [62]

चरण दर चरण

  1. सर्वर ऑर्डर करें: A = Hetzner Cloud (पोर्ट 25/465 डिफ़ॉल्ट रूप से ब्लॉक हैं; एक महीने और पहले इनवॉइस के भुगतान के बाद अनब्लॉक करने का अनुरोध करें, 587 खुला है), B = netcup VPS (SCP में फ़ायरवॉल नीति "netcup Mail block" हटाएँ), C = विटनेस के रूप में STRATO VPS (विटनेस को पोर्ट 25 की आवश्यकता नहीं है)। [13][26]
  2. PTR सेट करें: IP A को mx1.example.com (Hetzner Console), IP B को mx2.example.com (netcup SCP)। [14][27]
  3. cloud-init के माध्यम से बेस इंस्टॉल (Debian 13): deploy उपयोगकर्ता, Docker, WireGuard, पासवर्ड लॉगिन नहीं।
  4. A, B, C के बीच WireGuard मेश 10.88.0.0/24; UDP 51820 केवल पीयर IP के लिए खोलें। [47]
  5. तीनों पर etcd (election timeout कम से कम 10 × RTT), फिर A और B पर Patroni + PostgreSQL 18। [50][53]
  6. तीनों पर Garage, तीन ज़ोन वाला लेआउट लागू करें, बकेट और कुंजी बनाएँ। [61]
  7. A और B पर Docker Compose के माध्यम से nextxch (Cluster:Enabled=true, मल्टी-होस्ट कनेक्शन स्ट्रिंग, स्थानीय Garage की ओर इंगित S3)। /admin/ में होस्ट नाम mail.example.com सेट करें और mail, mx1, mx2 के लिए ACME प्रमाणपत्र जारी करें।
  8. DNS अध्याय 02 की तरह, /admin/ में MTA-STS MX सूची जाँचें, /admin/ में डोमेन के अंतर्गत DNS फेलओवर सक्षम करें (केवल क्लस्टर लीडर कार्य करता है) या वैकल्पिक रूप से etcd लॉक के अंतर्गत स्क्रिप्ट।
  9. अध्याय 09 के अनुसार बैकअप और एक रिस्टोर का परीक्षण करें। फिर एक आउटेज का अभ्यास करें: साइट A को हार्ड-स्टॉप करें और अध्याय 08 का पालन करें।
cloud-init (user data)
#cloud-config
# Hetzner Cloud / netcup: paste as user data when creating the VM.
users:
  - name: deploy
    groups: [sudo, docker]
    shell: /bin/bash
    sudo: "ALL=(ALL) NOPASSWD:ALL"
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... ci-deploy
package_update: true
packages: [wireguard, curl, jq, docker.io, docker-compose-v2, unattended-upgrades]
runcmd:
  - [ systemctl, enable, --now, docker ]
  - [ sed, -i, "s/^#\\?PasswordAuthentication.*/PasswordAuthentication no/", /etc/ssh/sshd_config ]
  - [ systemctl, restart, ssh ]
WireGuard: /etc/wireguard/wg0.conf
# /etc/wireguard/wg0.conf on site A (Hetzner). Same pattern on B and C.
# Full mesh: every site has one [Peer] block per other site.
[Interface]
Address    = 10.88.0.1/24
ListenPort = 51820
PrivateKey = <private-key-of-site-A>   # wg genkey, never commit it

[Peer]  # site B (netcup)
PublicKey           = <public-key-of-site-B>
Endpoint            = 198.51.100.20:51820
AllowedIPs          = 10.88.0.2/32
PersistentKeepalive = 25

[Peer]  # site C (witness or third site)
PublicKey           = <public-key-of-site-C>
Endpoint            = 203.0.113.30:51820
AllowedIPs          = 10.88.0.3/32
PersistentKeepalive = 25

# systemctl enable --now wg-quick@wg0
# Firewall: UDP 51820 open only for the two peer IPs.
etcd: /etc/default/etcd
# /etc/default/etcd on site A (Debian package etcd-server). Sites B and C analogue.
# Three members = quorum of two: any single site may fail.
ETCD_NAME="site-a"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_PEER_URLS="http://10.88.0.1:2380"
ETCD_LISTEN_CLIENT_URLS="http://10.88.0.1:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.88.0.1:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://10.88.0.1:2379"
ETCD_INITIAL_CLUSTER="site-a=http://10.88.0.1:2380,site-b=http://10.88.0.2:2380,site-c=http://10.88.0.3:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="nextxch-ha"
# WAN tuning (etcd tuning guide: election timeout at least 10x the round trip).
# Example for up to ~25 ms RTT between providers:
ETCD_HEARTBEAT_INTERVAL="250"
ETCD_ELECTION_TIMEOUT="2500"
# Traffic stays inside WireGuard. For production add TLS (ETCD_CERT_FILE etc.).
Patroni: /etc/patroni/config.yml
# /etc/patroni/config.yml on site A (Debian/PGDG package "patroni").
# Site B: name/addresses analogue. The witness C runs etcd only, no PostgreSQL.
scope: nextxch
name: pg-site-a

restapi:
  listen: 10.88.0.1:8008
  connect_address: 10.88.0.1:8008

etcd3:
  hosts: 10.88.0.1:2379,10.88.0.2:2379,10.88.0.3:2379

bootstrap:
  dcs:
    ttl: 30                 # defaults; rule: loop_wait + 2 * retry_timeout <= ttl
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    synchronous_mode: on    # a 250 OK to a sender is only given after the commit reached a 2nd site
    synchronous_mode_strict: false   # if the replica site is gone, keep accepting mail (falls back to async)
    failsafe_mode: true
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"
        archive_mode: "on"
        archive_command: "pgbackrest --stanza=nextxch archive-push %p"
  initdb: [ {encoding: UTF8}, data-checksums ]

postgresql:
  listen: 10.88.0.1:5432
  connect_address: 10.88.0.1:5432
  data_dir: /var/lib/postgresql/18/main
  bin_dir: /usr/lib/postgresql/18/bin
  authentication:
    replication: { username: replicator, password: "${PATRONI_REPLICATION_PASSWORD}" }
    superuser:   { username: postgres,   password: "${PATRONI_SUPERUSER_PASSWORD}" }
  pg_hba:
    - host replication replicator 10.88.0.0/24 scram-sha-256
    - host nextxch     nextxch    10.88.0.0/24 scram-sha-256
Garage: /etc/garage.toml
# /etc/garage.toml, identical on all three sites except rpc_public_addr.
metadata_dir = "/var/lib/garage/meta"
data_dir     = "/var/lib/garage/data"
db_engine    = "lmdb"

replication_factor = 3          # one copy per zone (site); writes need 2 of 3
consistency_mode   = "consistent"

rpc_bind_addr   = "10.88.0.1:3901"
rpc_public_addr = "10.88.0.1:3901"
rpc_secret      = "<openssl rand -hex 32, same on all nodes>"

[s3_api]
s3_region     = "garage"
api_bind_addr = "127.0.0.1:3900"   # each nextxch node talks to its local Garage

# After the first start (node ids from `garage node id`):
#   garage layout assign <id-a> -z hetzner -c 500G
#   garage layout assign <id-b> -z netcup  -c 500G
#   garage layout assign <id-c> -z witness -c 500G
#   garage layout show && garage layout apply --version 1
#   garage bucket create nextxch
#   garage key create nextxch-app
#   garage bucket allow --read --write --owner nextxch --key nextxch-app
nextxch: प्रति साइट docker-compose.yml
# docker-compose.yml on site A and site B (nextxch only; PostgreSQL/Patroni, etcd,
# Garage and WireGuard run as system services on the same VM).
# Secrets come from .env (never committed): NEXTXCH_LICENSE, PG_PASSWORD, S3_KEY, S3_SECRET
name: nextxch

services:
  nextxch:
    image: nextxch/server:${NEXTXCH_TAG}
    restart: unless-stopped
    network_mode: host            # real client IPs for SPF/DMARC, direct WireGuard reach
    environment:
      ASPNETCORE_URLS: "http://*:80;https://*:443"
      License__Token: "${NEXTXCH_LICENSE}"
      Cluster__Enabled: "true"
      Cluster__NodeId: "${SITE_NAME}"          # hetzner-a / netcup-b
      Storage__Provider: "Postgres"
      # Npgsql multi-host: always connects to the current Patroni primary
      Storage__PostgresConnectionString: "Host=10.88.0.1,10.88.0.2;Port=5432;Database=nextxch;Username=nextxch;Password=${PG_PASSWORD};Target Session Attributes=primary;Host Recheck Seconds=5"
      Storage__BlobBackend: "S3"
      Storage__Compression: "Zstd"
      Storage__S3__EndpointUrl: "http://127.0.0.1:3900"
      Storage__S3__Region: "garage"
      Storage__S3__BucketName: "nextxch"
      Storage__S3__ForcePathStyle: "true"
      Storage__S3__AccessKey: "${S3_KEY}"
      Storage__S3__SecretKey: "${S3_SECRET}"
      Smtp__ServerHostName: "mail.example.com"
      Imap__ServerHostName: "mail.example.com"
      ManageSieve__ServerHostName: "mail.example.com"
      Acme__Enabled: "true"
      Acme__ContactEmail: "postmaster@example.com"
    volumes:
      - nextxch-data:/app/data
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1/health/ready"]
      interval: 15s
      timeout: 5s
      retries: 3

volumes:
  nextxch-data:

कनेक्शन स्ट्रिंग दोनों PostgreSQL होस्ट को Target Session Attributes=primary के साथ नामित करती है। nextxch अपने कनेक्शन NpgsqlDataSource.Create (PostgresMailStore.cs) के माध्यम से बनाता है; कई होस्ट होने पर Npgsql स्वचालित रूप से एक मल्टी-होस्ट डेटा स्रोत बनाता है और केवल वर्तमान प्राइमरी से कनेक्ट होता है, हर Host Recheck Seconds (डिफ़ॉल्ट 10 s) पर स्थिति की फिर से जाँच करता है। Patroni फेलओवर के बाद नोड बिना किसी कॉन्फ़िगरेशन परिवर्तन के फिर से कनेक्ट हो जाते हैं; क्लस्टर LISTEN कनेक्शन हर 2 s पर पुनः प्रयास करता है (PostgresClusterCoordinator.cs)। [63]

mail.example.com के लिए स्वचालित DNS फेलओवर

nextxch एक DNS फेलओवर एजेंट के साथ आता है (opt-in, डिफ़ॉल्ट रूप से बंद; /admin/ में डोमेन के अंतर्गत, कार्ड "DNS failover")। हर नोड हर 30 s में हर नोड के सार्वजनिक IP के माध्यम से पोर्ट 25 पर SMTP बैनर और HTTPS /health जाँचता है। केवल क्लस्टर लीडर कार्रवाई करता है: वह एक PostgreSQL advisory lock रखता है, सभी नोड के दृष्टिकोण को बहुमत से मिलाता है और कॉन्फ़िगर किए गए नामों के A/AAAA रिकॉर्ड Hetzner DNS (Cloud API) या Cloudflare API के माध्यम से लिखता है। हिस्टैरिसीस: N विफल राउंड के बाद नोड DNS से हटता है, M स्वस्थ राउंड के बाद वापस आता है; अंतिम नोड कभी नहीं हटाया जाता। हर परिवर्तन इवेंट लॉग में दर्ज होता है, अलर्ट नियम "DNS failover: node taken out" ऑप्स अलर्ट के माध्यम से सूचित करता है। API टोकन सर्वर कीरिंग से एन्क्रिप्ट किया जाता है और फिर कभी नहीं दिखाया जाता।

बिल्ट-इन DNS फेलओवर एजेंट
# Admin: Domains > DNS failover (cluster). Stored cluster-wide; the token is encrypted
# with the server keyring and never shown again. Equivalent settings:
Provider:          Hetzner DNS (Cloud API, api.hetzner.cloud)   | Cloudflare DNS
Zone:              example.com
Names:             mail.example.com            # A/AAAA follow the healthy nodes
TTL:               60                          # resolvers keep old answers this long
Interval:          30 s
Failures (N):      3                           # 3 x 30 s before a node leaves DNS
Successes (M):     5                           # 5 x 30 s before it returns
Checks:            SMTP 25 (220 banner), HTTPS /health
Nodes:             fsn1, 192.0.2.10, 2001:db8::10
                   nbg1, 198.51.100.20
# Alert: rule "DNS failover: node taken out" (Alerts) fires when a node is out of DNS.
कटा हुआ नोड DNS क्यों नहीं बदलता
लीडर लॉक केवल PostgreSQL प्राइमरी से कनेक्शन के माध्यम से ही मौजूद होता है। कनेक्शन स्ट्रिंग में Target Session Attributes=primary होने पर (ऊपर अध्याय 04) कटा हुआ पक्ष किसी प्राइमरी तक नहीं पहुँचता, लॉक प्राप्त नहीं करता और निष्क्रिय रहता है। सभी पर्यवेक्षकों पर बहुमत मतदान किसी गलत दृष्टिकोण वाले एकल नोड को किसी स्वस्थ नोड को हटाने से रोकता है।

विकल्प: etcd लॉक के अंतर्गत स्क्रिप्ट

अंतर्निहित कनेक्टर के बिना DNS प्रदाताओं (उदा. deSEC) के लिए, या जब निर्णय etcd को लेना हो, तो स्क्रिप्ट एक समकक्ष विकल्प बनी रहती है। एक ही नामों के लिए एजेंट और स्क्रिप्ट को एक साथ न चलाएँ। स्क्रिप्ट हर 20 s पर हर नोड के /health/ready को उसके सार्वजनिक IP के माध्यम से जाँचती है और Cloudflare API (dns_records पर PATCH/POST/DELETE) के माध्यम से A रिकॉर्ड का मिलान करती है। यह हर साइट पर etcdctl lock के तहत चलती है: केवल एक प्रति कार्य करती है, और केवल उस पक्ष पर जहाँ etcd बहुमत है। इसलिए कटी हुई साइट अब DNS नहीं बदल सकती। Hetzner DNS या deSEC के लिए केवल तीन curl कॉल बदलते हैं। [39]

nextxch-dns-failover.sh
#!/usr/bin/env bash
# /usr/local/bin/nextxch-dns-failover.sh
# ALTERNATIVE to the built-in agent (admin: Domains > DNS failover), e.g. for DNS
# providers without a built-in connector (deSEC) or when etcd decides anyway.
# Keeps the A records of mail.example.com equal to the set of healthy nodes.
# Runs on every site under an etcd lock, so only one copy acts, and only on the
# side that still has etcd quorum (a cut-off minority site can never change DNS).
#   ExecStart=etcdctl --endpoints=http://127.0.0.1:2379 lock nextxch-dns /usr/local/bin/nextxch-dns-failover.sh
# Needs: curl, jq. Secrets via systemd EnvironmentFile (CF_API_TOKEN, CF_ZONE_ID).
set -euo pipefail

NAME="mail.example.com"
TTL=60
NODES=("192.0.2.10" "198.51.100.20")     # public IPs of the nextxch nodes
FAILS_BEFORE_REMOVE=3                    # 3 x 20 s = 1 minute
STATE=/run/nextxch-failover; mkdir -p "$STATE"
API="https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records"
AUTH=(-H "Authorization: Bearer ${CF_API_TOKEN}" -H "Content-Type: application/json")

healthy() {   # HTTPS readiness through the public IP, with SNI for the real name
  curl -fsS --max-time 5 --resolve "${NAME}:443:$1" "https://${NAME}/health/ready" \
    | jq -e '.status == "ready"' >/dev/null
}

while true; do
  want=()
  for ip in "${NODES[@]}"; do
    f="$STATE/$ip"; n=$(cat "$f" 2>/dev/null || echo 0)
    if healthy "$ip"; then echo 0 > "$f"; want+=("$ip")
    else n=$((n+1)); echo "$n" > "$f"; [ "$n" -lt "$FAILS_BEFORE_REMOVE" ] && want+=("$ip"); fi
  done
  # Never publish an empty set: if everything looks down, the checker is probably the problem.
  [ "${#want[@]}" -eq 0 ] && { sleep 20; continue; }

  have=$(curl -fsS "${AUTH[@]}" "${API}?type=A&name=${NAME}" | jq -r '.result[] | "\(.id) \(.content)"')
  for ip in "${want[@]}"; do   # add missing healthy IPs
    grep -q " ${ip}$" <<<"$have" || curl -fsS "${AUTH[@]}" -X POST "$API" \
      --data "{\"type\":\"A\",\"name\":\"${NAME}\",\"content\":\"${ip}\",\"ttl\":${TTL},\"proxied\":false}" >/dev/null
  done
  while read -r id ip; do      # remove IPs that are no longer healthy
    [ -z "${id:-}" ] && continue
    printf '%s\n' "${want[@]}" | grep -qx "$ip" || curl -fsS "${AUTH[@]}" -X DELETE "${API}/${id}" >/dev/null
  done <<<"$have"
  sleep 20
done
systemd यूनिट
# /etc/systemd/system/nextxch-dns-failover.service (install on every site)
[Unit]
Description=nextxch DNS failover (runs only while holding the etcd lock)
After=network-online.target etcd.service wg-quick@wg0.service
Wants=network-online.target

[Service]
EnvironmentFile=/etc/nextxch/dns-failover.env
ExecStart=/usr/bin/etcdctl --endpoints=http://127.0.0.1:2379 lock nextxch-dns /usr/local/bin/nextxch-dns-failover.sh
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

05 · सेटअप M: तीन पूर्ण साइटें (Proxmox या सामान्य VM)

सेटअप S की तरह, लेकिन तीनों साइटें PostgreSQL रेप्लिका के साथ nextxch चलाती हैं। जब एक साइट विफल होती है, तो दो डेटाबेस और दो nextxch नोड बचे रहते हैं, और synchronous_mode के साथ सिंक्रोनस रेप्लिकेशन भी बना रहता है। समान प्राथमिकता वाले तीन MX रिकॉर्ड इनबाउंड लोड को फैलाते हैं।

flowchart TB
  S["Remote mail servers"] -->|"MX 10 mx1, mx2, mx3"| N
  U["Users"] -->|"mail.example.com"| N
  subgraph N["WireGuard mesh 10.88.0.0/24"]
    direction LR
    subgraph A["Hetzner"]
      A1["nextxch"] --- A2[("Patroni + PG")] --- A3["etcd + Garage"]
    end
    subgraph B["netcup"]
      B1["nextxch"] --- B2[("Patroni + PG")] --- B3["etcd + Garage"]
    end
    subgraph C["Third provider"]
      C1["nextxch"] --- C2[("Patroni + PG")] --- C3["etcd + Garage"]
    end
  end
  N --> BK["Backups: Storage Box + S3 at another provider"]
आर्किटेक्चर सेटअप M
Proxmox: प्रदाताओं के पार कोई क्लस्टर नहीं
Proxmox के अनुसार, corosync को 5 ms से कम विलंबता (LAN स्तर) चाहिए; लगभग 10 ms से अधिक और तीन से अधिक नोड पर इसके काम करने की संभावना कम है। इसलिए Hetzner, netcup और STRATO में फैले एकल Proxmox VE क्लस्टर की अनुशंसा नहीं की जाती। सही पैटर्न: प्रति प्रदाता एक स्वतंत्र Proxmox होस्ट या क्लस्टर, और उच्च उपलब्धता एप्लिकेशन द्वारा (Patroni, Garage, nextxch क्लस्टर)। QDevice (corosync-qnetd) एक साइट पर दो-नोड क्लस्टर के लिए है और विलंबता सीमा से बंधा नहीं है। Proxmox Datacenter Manager (2025-12-04 से 1.0) कई क्लस्टरों को एक साथ प्रबंधित करता है। [45]

चरण दर चरण

  1. तीन प्रदाताओं पर तीन VM (या Proxmox गेस्ट), एक ही Debian संस्करण, प्रत्येक का PTR mx1/mx2/mx3 पर।
  2. तीन होस्ट वाली Ansible इन्वेंटरी, सभी के लिए एक प्लेबुक (WireGuard, etcd, Patroni, Garage, nextxch)।
  3. सेटअप S का Patroni कॉन्फ़िगरेशन, एक तीसरे होस्ट के साथ; synchronous_mode: on, तीन डेटाबेस के साथ वैकल्पिक रूप से quorum। [54]
  4. तीन होस्ट वाली कनेक्शन स्ट्रिंग, तीन ज़ोन वाला Garage लेआउट, तीन MX रिकॉर्ड, SPF और DNS फेलओवर नोड सूची में तीन IP।
  5. VM इमेज के लिए Proxmox Backup Server वैकल्पिक: डेटास्टोर स्थानीय या S3 (PBS 4.2 से S3 बैकएंड आधिकारिक रूप से समर्थित); Proxmox, CIFS के माध्यम से Storage Box जैसे नेटवर्क शेयर को डेटास्टोर प्रकार के रूप में प्रलेखित नहीं करता। [46]
Ansible: inventory/ha.yml
# inventory/ha.yml: three sites, one VM (or Proxmox guest) per site
all:
  vars:
    ansible_user: deploy
    wg_network: 10.88.0.0/24
    pg_major: 18
  children:
    nextxch_sites:
      hosts:
        hetzner-a: { ansible_host: 192.0.2.10,    wg_ip: 10.88.0.1, garage_zone: hetzner }
        netcup-b:  { ansible_host: 198.51.100.20, wg_ip: 10.88.0.2, garage_zone: netcup }
        third-c:   { ansible_host: 203.0.113.30,  wg_ip: 10.88.0.3, garage_zone: third }
Ansible: site.yml
# site.yml: idempotent base for every site (same playbook for Hetzner, netcup, Strato ...)
- hosts: nextxch_sites
  become: true
  tasks:
    - name: Base packages (Debian 13)
      ansible.builtin.apt:
        name: [wireguard, etcd-server, etcd-client, patroni, pgbackrest, docker.io, docker-compose-v2, jq, curl]
        update_cache: true
    # PostgreSQL 18 itself comes from the PGDG apt repository (apt.postgresql.org),
    # Garage as the static binary or the dxflrs/garage image.

    - name: WireGuard config
      ansible.builtin.template: { src: wg0.conf.j2, dest: /etc/wireguard/wg0.conf, mode: "0600" }
      notify: restart wg

    - name: etcd, Patroni and Garage config
      ansible.builtin.template: { src: "{{ item.src }}", dest: "{{ item.dest }}", mode: "0640" }
      loop:
        - { src: etcd.env.j2,     dest: /etc/default/etcd }
        - { src: patroni.yml.j2,  dest: /etc/patroni/config.yml }
        - { src: garage.toml.j2,  dest: /etc/garage.toml }

    - name: nextxch compose file
      ansible.builtin.template: { src: docker-compose.yml.j2, dest: /opt/nextxch/docker-compose.yml }

    - name: Start nextxch
      ansible.builtin.command: docker compose up -d
      args: { chdir: /opt/nextxch }
      changed_when: false

  handlers:
    - name: restart wg
      ansible.builtin.service: { name: wg-quick@wg0, state: restarted }

06 · सेटअप L: तीन प्रदाताओं पर Kubernetes

बहुत सारे मेलबॉक्स और क्षैतिज स्केलिंग के लिए। पहले एक महत्वपूर्ण बात: k3s कई क्लाउड में फैले क्लस्टर में एम्बेडेड etcd को स्पष्ट रूप से समर्थित नहीं करता; सर्वर नोड एक ही स्थान पर रहने चाहिए। इसलिए: प्रति प्रदाता एक Kubernetes क्लस्टर (प्रत्येक k3s या Talos), जिनमें से प्रत्येक साथ दिए गए Helm चार्ट से एक nextxch डिप्लॉयमेंट चलाता है। डेटा लेयर (Patroni + etcd + Garage) सेटअप M की तरह WireGuard पर अपने अलग VM पर चलती है और तीनों क्लस्टर इसका उपयोग करते हैं। [49][48]

flowchart TB
  DNS["DNS: MX per provider LB, mail.example.com with health checks"]
  subgraph K1["k3s/Talos cluster Hetzner"]
    L1["Load balancer"] --> P1["nextxch pods (HPA)"]
  end
  subgraph K2["k3s/Talos cluster netcup"]
    L2["Load balancer"] --> P2["nextxch pods (HPA)"]
  end
  subgraph K3["k3s/Talos cluster STRATO"]
    L3["Load balancer"] --> P3["nextxch pods (HPA)"]
  end
  DNS --> L1
  DNS --> L2
  DNS --> L3
  subgraph D["Data layer on VMs, WireGuard"]
    PG[("Patroni: primary + 2 replicas")]
    GA["Garage: 3 zones"]
    ET["etcd: 3 members"]
  end
  P1 --> PG
  P2 --> PG
  P3 --> PG
  P1 --> GA
  P2 --> GA
  P3 --> GA
आर्किटेक्चर सेटअप L

क्लस्टरों के बीच CloudNativePG क्यों नहीं?

CloudNativePG अन्य Kubernetes क्लस्टर में रेप्लिका क्लस्टर चला सकता है, लेकिन क्लस्टरों के बीच प्रमोशन और डिमोशन घोषणात्मक हैं (फ़ील्ड primary, promotionToken), यानी स्वचालित नहीं। स्वचालित प्रदाता फेलओवर के लिए तीन साइटों पर etcd के साथ Patroni अधिक सीधा समाधान है। [52]

सभी प्रदाताओं में एक ही क्लस्टर?

Talos, KubeSpan प्रदान करता है, जो साइटों के बीच एक WireGuard फुल मेश है, और k3s एक flannel बैकएंड wireguard-native। हालाँकि कंट्रोल प्लेन (etcd) WAN लेटेंसी के प्रति संवेदनशील है। कई छोटे क्लस्टर चलाना आसान है और वे स्वतंत्र रूप से विफल होते हैं। [51][49][50]

चरण दर चरण

  1. डेटा लेयर को सेटअप M की तरह बनाएँ (Patroni, etcd, Garage के साथ तीन VM)।
  2. प्रति प्रदाता एक k3s या Talos क्लस्टर; WireGuard (या KubeSpan) के माध्यम से डेटा लेयर से नेटवर्क लिंक।
  3. इनबाउंड पोर्ट: externalTrafficPolicy: Local के साथ LoadBalancer प्रकार की सेवा (रिपॉज़िटरी की तरह), ताकि SPF/DMARC के लिए प्रेषक IP सुरक्षित रहे; PROXY प्रोटोकॉल समर्थित लोड बैलेंसर के साथ इसके बजाय Cluster__EnableProxyProtocol=true का उपयोग करें। Hetzner Cloud Load Balancer PROXY प्रोटोकॉल और किसी भी TCP सेवा का समर्थन करता है। [19]
  4. आउटबाउंड पोर्ट 25: मेल खाने वाले PTR और SPF प्रविष्टि वाले एक निश्चित IP के माध्यम से पॉड egress (egress गेटवे, जैसा कि deploy/kubernetes/README.md में वर्णित है)।
  5. प्रति प्रदाता Helm values, पाइपलाइन के माध्यम से एक के बाद एक रोलआउट (अध्याय 07)। HPA (70% CPU पर 2 से 10 पॉड) और PodDisruptionBudget रिपॉज़िटरी के साथ आते हैं।
Helm: k8s/values-hetzner.yaml
# values-hetzner.yaml (one file per provider cluster; netcup/strato analogue)
# Helm chart: deploy/helm/nextxch in the nextxch repository.
replicaCount: 3
mail:
  hostname: mail.example.com
  defaultDomain: example.com
postgres:
  # Patroni data layer outside Kubernetes, reached through WireGuard / KubeSpan
  connectionString: "Host=10.88.0.1,10.88.0.2,10.88.0.3;Database=nextxch;Username=nextxch;Password=<from secret>;Target Session Attributes=primary"
s3:
  endpointUrl: "http://garage.nextxch-data.svc:3900"   # Garage zone of this provider
  bucketName: "nextxch"

07 · Git से डिप्लॉय और अपडेट

हर महत्वपूर्ण चीज़ रिपॉज़िटरी में एक फ़ाइल है: compose फ़ाइल या Helm values, Ansible इन्वेंटरी, DNS फेलओवर स्क्रिप्ट। पाइपलाइन हमेशा केवल एक साइट पर रोल आउट करती है और आगे बढ़ने से पहले तब तक प्रतीक्षा करती है जब तक /health/ready फिर से "ready" न बताए। सीक्रेट (लाइसेंस, पासवर्ड, API टोकन, SSH कुंजियाँ) केवल GitHub/GitLab सीक्रेट स्टोर में या सर्वर पर .env में रहते हैं, कभी भी रिपॉज़िटरी में नहीं।

flowchart TB
  G["git push to main"] --> V["Validate: compose config / helm lint"]
  V --> A["Roll out site A"]
  A --> HA{"/health/ready = ready?"}
  HA -->|"yes"| B["Roll out site B"]
  HA -->|"no"| X["Abort, B stays on old version"]
  B --> HB{"/health/ready = ready?"}
  HB -->|"yes"| C["Roll out site C"]
  HB -->|"no"| X
  C --> OK["Done"]
परिनियोजन पाइपलाइन
GitHub Actions: Compose साइटें
# .github/workflows/deploy.yml: rolling update of the compose sites, one site at a time.
name: deploy-nextxch
on:
  push:
    branches: [main]
    paths: ["sites/**", "compose/**"]
  workflow_dispatch:

concurrency: { group: deploy-nextxch, cancel-in-progress: false }

jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 1            # never update two sites at once
      matrix:
        site: [hetzner-a, netcup-b]
    environment: ${{ matrix.site }}
    steps:
      - uses: actions/checkout@v4
      - name: Write SSH key
        run: |
          install -m 700 -d ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
          echo "${{ vars.HOST_KEY }}" >> ~/.ssh/known_hosts
      - name: Upload compose file and roll the container
        env:
          HOST: ${{ vars.SSH_HOST }}
        run: |
          scp compose/docker-compose.yml "deploy@${HOST}:/opt/nextxch/docker-compose.yml"
          scp "sites/${{ matrix.site }}.env.tag" "deploy@${HOST}:/opt/nextxch/tag.env"
          ssh "deploy@${HOST}" 'cd /opt/nextxch && docker compose --env-file .env --env-file tag.env pull && docker compose --env-file .env --env-file tag.env up -d'
      - name: Wait until the node is ready again
        env:
          HOST: ${{ vars.SSH_HOST }}
        run: |
          for i in $(seq 1 30); do
            ssh "deploy@${HOST}" 'curl -fsS http://127.0.0.1/health/ready' && exit 0
            sleep 10
          done
          echo "node did not become ready" >&2; exit 1
GitLab CI
# .gitlab-ci.yml: the same rolling update with GitLab CI
stages: [validate, deploy-a, deploy-b]

variables:
  NEXTXCH_TAG: "latest"     # pin a released version in production

validate:
  stage: validate
  image: docker:27
  script:
    - docker compose -f compose/docker-compose.yml config -q

.deploy: &deploy
  image: alpine:3.22
  before_script:
    - apk add --no-cache openssh-client curl
    - install -m 700 -d ~/.ssh && echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
    - echo "$HOST_KEY" >> ~/.ssh/known_hosts
  script:
    - scp compose/docker-compose.yml "deploy@$SSH_HOST:/opt/nextxch/"
    - ssh "deploy@$SSH_HOST" "cd /opt/nextxch && NEXTXCH_TAG=$NEXTXCH_TAG docker compose pull && NEXTXCH_TAG=$NEXTXCH_TAG docker compose up -d"
    - for i in $(seq 1 30); do ssh "deploy@$SSH_HOST" 'curl -fsS http://127.0.0.1/health/ready' && exit 0; sleep 10; done; exit 1
  resource_group: production   # serializes deployments
  rules: [ { if: '$CI_COMMIT_BRANCH == "main"' } ]

deploy-hetzner-a:
  <<: *deploy
  stage: deploy-a
  environment: { name: hetzner-a }

deploy-netcup-b:
  <<: *deploy
  stage: deploy-b
  environment: { name: netcup-b }
GitHub Actions: Kubernetes (सेटअप L)
# .github/workflows/deploy-k8s.yml: one Kubernetes cluster per provider, rolled one after another.
name: deploy-nextxch-k8s
on:
  push: { branches: [main], paths: ["k8s/**"] }
  workflow_dispatch:

concurrency: { group: deploy-k8s, cancel-in-progress: false }

jobs:
  helm:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 1
      matrix:
        cluster: [hetzner, netcup, strato]
    environment: k8s-${{ matrix.cluster }}
    steps:
      - uses: actions/checkout@v4
      - uses: azure/setup-helm@v4
      - name: Kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config
          chmod 600 ~/.kube/config
      - name: Helm upgrade (waits for readiness probes on /health/ready)
        run: |
          helm upgrade --install nextxch ./deploy/helm/nextxch \
            --namespace nextxch --create-namespace \
            -f k8s/values-common.yaml -f k8s/values-${{ matrix.cluster }}.yaml \
            --set image.tag=${{ vars.NEXTXCH_TAG }} \
            --wait --timeout 10m --atomic
फेलओवर जॉब CI में नहीं होना चाहिए
शेड्यूल किए गए CI रन न तो सेकंड तक सटीक होते हैं और न ही गारंटीड। इसलिए DNS फेलओवर स्वयं साइटों पर चलता है: nextxch में बिल्ट-इन एजेंट के रूप में या स्क्रिप्ट के साथ systemd सेवा के रूप में (अध्याय 04), जिसे पाइपलाइन केवल रोल आउट करती है।
मेल कॉन्फ़िगरेशन भी Git से
डोमेन, मेलबॉक्स, सूचियाँ, नियम, SMTP और अन्य सेटिंग्स भी रिपॉज़िटरी में YAML के रूप में रहती हैं: nextxch-cli config plan पुल रिक्वेस्ट पर प्लान के साथ टिप्पणी करता है, config apply --prune --yes मर्ज के बाद उसे लागू करता है। तैयार GitHub Actions और GitLab CI फ़ाइलों के साथ गाइड: कॉन्फ़िगरेशन ऐज़ कोड (GitOps)।

08 · विफलता परिदृश्य: ठीक-ठीक क्या होता है

Hetzner (साइट A, वर्तमान प्राइमरी) पूरी तरह ऑफ़लाइन हो जाता है

sequenceDiagram
  participant S as Remote mail server
  participant A as Site A (Hetzner)
  participant B as Site B (netcup)
  participant C as Witness C
  participant D as DNS
  Note over A: Provider outage
  S->>A: SMTP to mx1
  A--xS: no connection
  S->>B: SMTP to mx2 (or retry)
  B--xS: without a primary the session fails, sender retries later
  Note over B,C: etcd majority B + C, leader key of A expires (ttl 30 s)
  B->>B: Patroni promotes PostgreSQL B to primary
  B->>B: nextxch reconnects (Target Session Attributes=primary)
  S->>B: retry
  B-->>S: 250 OK (commit synchronous or asynchronous)
  C->>D: failover job removes IP A from mail.example.com
  Note over D: clients follow after the TTL expires
साइट A विफल होने पर क्रम
समयक्या होता हैउपयोगकर्ता क्या महसूस करते हैं
0 sA उपलब्ध नहीं है। mx1 पर इनबाउंड मेल विफल होता है, प्रेषक mx2 (समान प्राथमिकता) का उपयोग करते हैं या बाद में पुनः प्रयास करते हैं। [1]A पर क्लाइंट का कनेक्शन टूट जाता है।
लगभग 30 s तकetcd में A की लीडर कुंजी ttl = 30 s के बाद समाप्त हो जाती है; B और C के पास बहुमत है। [53]नोड B /health/ready पर 503 लौटाता है, लेखन प्रतीक्षा करते हैं।
लगभग 30 से 60 sPatroni, B को प्रमोट करता है (loop_wait 10 s)। B पर nextxch मल्टी-होस्ट कनेक्शन स्ट्रिंग के माध्यम से नया प्राइमरी ढूँढता है (recheck 5 से 10 s)। [63]B फिर से मेल स्वीकार करता है।
लगभग 60 से 90 sDNS फेलओवर (एजेंट: 3 विफल राउंड × 30 s, स्क्रिप्ट: 3 × 20 s) mail.example.com से IP A हटा देता है।TTL (60 s) समाप्त होते ही नए कनेक्शन B पर जाते हैं।
1 से 5 मिनटअपने स्वयं के न्यूनतम TTL वाले रिज़ॉल्वर और DNS कैश वाले क्लाइंट बाद में अनुसरण करते हैं। Garage ज़ोन B और C के साथ चलता रहता है (3 में से 2 का कोरम)। [61]क्लाइंट फिर से कनेक्ट होते हैं; वेबमेल सत्र मान्य रहते हैं (हस्ताक्षरित, स्टेटलेस सत्र)।

RPO

synchronous_mode के साथ, PostgreSQL किसी commit की पुष्टि तभी करता है जब वह दूसरी साइट पर पहुँच चुका हो; 250 OK से पुष्टि किया गया संदेश तब दो साइटों पर मौजूद होता है। कीमत: हर commit साइटों के बीच कम से कम एक राउंड ट्रिप की प्रतीक्षा करता है। synchronous_mode के बिना आउटेज से पहले के अंतिम सेकंड गायब हो सकते हैं। synchronous_mode_strict: false के साथ, कोई रेप्लिका न बचने पर Patroni एसिंक्रोनस पर लौट आता है, ताकि मेल स्वीकार होता रहे। [56][54]

RTO

डेटाबेस लगभग 30 से 60 s (Patroni डिफ़ॉल्ट), DNS स्विच लगभग 1 मिनट और TTL, इनबाउंड मेल दूसरे MX या प्रेषक के पुनः प्रयास के माध्यम से बिना प्रतीक्षा के। ये समय डिफ़ॉल्ट से निकाले गए हैं, मापे नहीं गए: अपने स्वयं के परीक्षण में सत्यापित करें (किसी साइट को हार्ड-स्टॉप करें)।

netcup (साइट B, रेप्लिका) ऑफ़लाइन हो जाता है

A पर प्राइमरी चलता रहता है। सिंक्रोनस पार्टनर के बिना Patroni एसिंक्रोनस पर स्विच करता है (strict = false), फेलओवर जॉब IP B हटा देता है, इनबाउंड मेल mx1 से प्रवाहित होता है। Garage A और C के साथ जारी रहता है।

केवल A और B के बीच का लिंक बंद है

जो पक्ष अब भी विटनेस C तक पहुँचता है, वह etcd बहुमत बनाए रखता है और प्राइमरी बना रहता है या बन जाता है। दूसरा पक्ष अपना बहुमत खो देता है, Patroni वहाँ प्राइमरी को डिमोट कर देता है (failsafe_mode केवल तभी लागू होता है जब वह अब भी सभी सदस्यों तक पहुँच सके), और DNS फेलओवर वहाँ कार्रवाई नहीं करता: बिल्ट-इन एजेंट किसी प्राइमरी तक नहीं पहुँचता और उसे लीडर लॉक नहीं मिलता, स्क्रिप्ट को etcd लॉक नहीं मिलता। कोई स्प्लिट ब्रेन नहीं होता। [55]

फेलबैक

sequenceDiagram
  participant A as Site A
  participant B as Site B (primary)
  participant G as Garage
  participant D as DNS job
  A->>A: server back online, WireGuard up
  A->>B: Patroni: pg_rewind, then replica of B
  G->>A: missing objects are re-replicated
  A->>A: /health/ready = ready
  D->>D: successful checks
  D->>A: IP A back in mail.example.com
  Note over A,B: optional, planned: patronictl switchover back to A
साइट A की वापसी

जब A वापस आता है, तो B प्राइमरी बना रहता है। Patroni, pg_rewind (use_pg_rewind: true) के साथ A को रेप्लिका के रूप में वापस लाता है, Garage लुप्त ऑब्जेक्ट को फिर से सिंक करता है, और जैसे ही वहाँ /health/ready "ready" रिपोर्ट करता है, फेलओवर जॉब IP A को फिर से जोड़ देता है। प्राइमरी को वापस ले जाना आवश्यक नहीं है; यदि चाहें, तो इसे शांत समय पर patronictl switchover से योजनाबद्ध तरीके से करें।

09 · बैकअप, पुनर्स्थापना और GoBD

प्रतिकृति आउटेज से बचाती है, गलतियों से नहीं: गलती से हटाया गया मेलबॉक्स एक साथ सभी साइटों से चला जाता है। इसलिए 3-2-1 नियम के अनुसार बैकअप जोड़ें: तीन प्रतियाँ, दो मीडिया प्रकार, एक ऑफ़साइट। [59]

flowchart LR
  PG[("PostgreSQL primary or replica")] -->|"pgBackRest, continuous WAL"| R1["Repo 1: Hetzner Storage Box (SFTP)"]
  PG -->|"pgBackRest"| R2["Repo 2: S3 at another provider"]
  GA["Garage (3 zones)"] -->|"rclone copy hourly"| AR["Object Storage with Object Lock (GoBD)"]
  GA -->|"rclone copy"| R1
  R1 --> T["Quarterly restore test"]
  R2 --> T
बैकअप प्रवाह
  • PostgreSQL: सतत WAL आर्काइविंग के साथ pgBackRest (point-in-time recovery)। रिपॉज़िटरी 1 SFTP के माध्यम से Hetzner Storage Box पर (pgBackRest 2.46 से SFTP का समर्थन करता है; Storage Box SFTP, SCP, rsync, BorgBackup, Restic, Rclone, SMB, WebDAV प्रदान करता है), रिपॉज़िटरी 2 किसी अन्य प्रदाता के S3 पर। दोनों एन्क्रिप्टेड। [58][15]
  • ब्लॉब: .eml ऑब्जेक्ट का नाम SHA-256 से रखा जाता है और उन्हें कभी संशोधित नहीं किया जाता। प्रति घंटे की कॉपी के रूप में rclone copy (sync नहीं, ताकि यह कभी कुछ न हटाए) पर्याप्त है।
  • GoBD आर्काइव: Hetzner Object Storage, GOVERNANCE और COMPLIANCE मोड में Object Lock तथा वर्ज़निंग का समर्थन करता है; Object Lock बकेट बनाते समय ही सक्षम करना होगा। बकेट को डिफ़ॉल्ट प्रतिधारण (compliance) के साथ बनाएँ, फिर हर कॉपी किया गया ऑब्जेक्ट स्वचालित रूप से लॉक हो जाता है। वैकल्पिक रूप से nextxch को सीधे इस बकेट में लिखने दें (S3 या Hybrid बैकएंड), लेकिन तब ब्लॉब की उपलब्धता Hetzner पर निर्भर करती है। उस स्थिति में WORM मोड कॉन्फ़िगर होते ही (या Storage:S3:ObjectLockEnabled) nextxch स्टार्टअप पर अनुपलब्ध बकेट को Object Lock के साथ बनाता है; GoBD आर्काइव में "S3 बकेट जाँचें" पहुँच, वर्ज़निंग, Object Lock मोड और डिफ़ॉल्ट प्रतिधारण दिखाता है। [18][17]
  • रिस्टोर का परीक्षण करें: कम से कम हर तिमाही pgBackRest को एक टेस्ट VM में रिस्टोर करें और blob प्रति से नमूने खोलें।
pgBackRest: /etc/pgbackrest/pgbackrest.conf
# /etc/pgbackrest/pgbackrest.conf on every PostgreSQL site.
# Repo 1: Hetzner Storage Box over SFTP (pgBackRest 2.46+), repo 2: S3 at another provider.
[global]
repo1-type=sftp
repo1-path=/pgbackrest
repo1-sftp-host=uXXXXXX.your-storagebox.de
repo1-sftp-host-port=23
repo1-sftp-host-user=uXXXXXX
repo1-sftp-private-key-file=/var/lib/postgresql/.ssh/id_ed25519
repo1-sftp-host-key-hash-type=sha256
repo1-retention-full=2
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=<long random secret, stored outside the repo>

repo2-type=s3
repo2-path=/pgbackrest
repo2-s3-endpoint=<s3 endpoint of the second provider>
repo2-s3-bucket=nextxch-pg-backup
repo2-s3-region=<region>
repo2-s3-uri-style=path
repo2-retention-full=4
repo2-cipher-type=aes-256-cbc
repo2-cipher-pass=<second secret>

archive-async=y
process-max=2
start-fast=y

[nextxch]
pg1-path=/var/lib/postgresql/18/main
pg1-port=5432

# Weekly full, daily differential (cron on every node; pgBackRest runs only where it can,
# use --repo and the "backup-standby" option to take backups from a replica).
#   0 2 * * 0  pgbackrest --stanza=nextxch --type=full backup
#   0 2 * * 1-6 pgbackrest --stanza=nextxch --type=diff backup
rclone: ब्लॉब कॉपी और GoBD बकेट
# Blob copy (all .eml objects) from Garage to an independent target, every hour.
# ~/.config/rclone/rclone.conf (keys are placeholders)
[garage]
type = s3
provider = Other
endpoint = http://127.0.0.1:3900
region = garage
access_key_id = <garage key>
secret_access_key = <garage secret>

[archive]
type = s3
provider = Other
endpoint = <object storage endpoint>
access_key_id = <key>
secret_access_key = <secret>

# Content-addressed (SHA-256 names, never modified): "copy" is enough and never deletes.
#   rclone copy garage:nextxch archive:nextxch-archive --immutable --transfers 8 --checksum
# For a GoBD copy create the target bucket WITH Object Lock and a default retention
# (COMPLIANCE, e.g. 10 years) so every copied object is locked automatically.

10 · स्केलिंग: उपयोगकर्ता, स्टोरेज, पॉड

flowchart LR
  S1["Start: 2 sites + witness"] --> S2["More mailboxes: add nextxch nodes per site"]
  S2 --> S3["DB grows: scale PostgreSQL VMs vertically (RAM, NVMe)"]
  S3 --> S4["More storage: add Garage nodes per zone"]
  S4 --> S5["Many mailboxes: Kubernetes per provider, HPA"]
स्केलिंग पथ
घटकस्केलिंगनोट
nextxch नोड / पॉडक्षैतिज रूप सेस्टेटलेस; प्रति साइट अधिक नोड या अधिक पॉड (HPA)। स्टिकी सत्र आवश्यक नहीं।
PostgreSQL प्राइमरीवर्टिकल रूप सेसभी लेखन (writes) एक ही प्राइमरी पर जाते हैं। अधिक नोड के बजाय अधिक RAM और तेज़ डिस्क (IOPS); shared_buffers का प्रारंभिक मान RAM का 25%। nextxch रीड रेप्लिका का उपयोग नहीं करता। [57]
ब्लॉब स्टोरक्षैतिज रूप सेप्रति ज़ोन क्षमता के साथ अधिक Garage नोड (garage layout assign -c), डेटा पुनः संतुलित होता है। SHA-256 डीडुप्लिकेशन और Zstd संपीड़न इसकी आवश्यकता कम करते हैं।
SMTPDNS के माध्यम सेसमान प्राथमिकता वाले अधिक MX रिकॉर्ड लोड को फैलाते हैं; आउटबाउंड कतार SKIP LOCKED के माध्यम से सभी नोड पर फैलती है।
IMAPDNS / LB के माध्यम सेलंबे समय तक चलने वाले IDLE कनेक्शन अपने नोड पर रहते हैं; अन्य नोड से नई मेल LISTEN/NOTIFY के माध्यम से आती है। नोड जोड़ने के बाद, लोड केवल तभी फैलता है जब क्लाइंट दोबारा कनेक्ट होते हैं। [7]
HTTPS (वेबमेल, ActiveSync, JMAP)DNS / LB के माध्यम सेस्टेटलेस HMAC सत्र; कोई भी अनुरोध किसी भी नोड पर पहुँच सकता है।

11 · लागत: कहाँ बचत करें, कहाँ नहीं

सहेजें

  • nextxch और विटनेस के लिए साझा vCPU प्लान; समर्पित कोर केवल तब जब डेटाबेस CPU लोड इसकी आवश्यकता दिखाए।
  • netcup पर 12 महीने की अवधि (मूल्य पेज के अनुसार मासिक मूल्य से 13% कम)। [22]
  • सशुल्क लोड बैलेंसर के बजाय बिल्ट-इन एजेंट (या अपनी स्क्रिप्ट) के साथ DNS फेलओवर।
  • ब्लॉब के लिए Zstd कम्प्रेशन (Storage:Compression) और SHA-256 डिडुप्लीकेशन।

इसमें बचत न करें

  • PostgreSQL नोड की RAM: जो कुछ भी कैश नहीं है, वह हर फ़ोल्डर दृश्य पर डिस्क IO खर्च करता है।
  • PostgreSQL (NVMe) के लिए डिस्क IOPS; WAL राइट हर डिलीवरी की लेटेंसी तय करते हैं।
  • तीसरी साइट: इसके बिना कोई सुरक्षित स्वचालित फेलओवर नहीं होता।
  • एक स्वतंत्र लक्ष्य पर परीक्षित बैकअप।

संग्रहीत मूल्य (शुद्ध प्रति माह, 2 नवंबर 2026 की स्थिति)। netcup और STRATO 19% VAT सहित सकल मूल्य दिखाते हैं, जिन्हें यहाँ 1.19 से विभाजित किया गया है। Hetzner के लिए प्राथमिक IPv4 के €0.50 जोड़े गए हैं। जाँच की तिथि पर Hetzner ने सभी क्लाउड प्लान को "currently unavailable" दिखाया; यह स्टॉक के कारण था या प्रदर्शन के कारण, यह निर्धारित नहीं किया जा सका। Hetzner वॉल्यूम का मूल्य आधिकारिक पेजों से पढ़ा नहीं जा सका, इसलिए यह कैलकुलेटर में नहीं है। मूल्य बदलते रहते हैं: ऑर्डर करने से पहले प्रदाता से जाँच करें। [12][10][22][23][30][15][16]

प्रदातायोजनाvCPURAMडिस्क€ शुद्ध / माह
Hetzner CloudCX2324 GB40 GB€6.49
Hetzner CloudCX3348 GB80 GB€9.49
Hetzner CloudCX43816 GB160 GB€16.99
Hetzner CloudCX531632 GB320 GB€30.49
netcup VPSVPS 500 G12.524 GB64 GB€6.94
netcup VPSVPS 1000 G12.548 GB128 GB€12.18
netcup VPSVPS 2000 G12.5816 GB256 GB€22.62
netcup VPSVPS 4000 G12.51232 GB512 GB€38.12
netcup VPSVPS 8000 G12.51664 GB1024 GB€56.39
netcup Root ServerRS 1000 G12.548 GB128 GB€18.26
netcup Root ServerRS 2000 G12.5816 GB256 GB€34.20
netcup Root ServerRS 4000 G12.51232 GB512 GB€65.40
netcup Root ServerRS 8000 G12.51664 GB1024 GB€124.72
netcup Root ServerRS 12000 G12.52096 GB1536 GB€182.26
netcup Root ServerRS 16000 G12.524128 GB2048 GB€243.24
STRATO VPSVPS S12 GB60 GB€3.36
STRATO VPSVPS M24 GB120 GB€8.40
STRATO VPSVPS L48 GB240 GB€16.81
STRATO VPSVPS XL816 GB480 GB€32.77
STRATO VPSVPS XXL1224 GB720 GB€47.06
Hetzner Storage BoxBX11--1 TB€3.20
Hetzner Storage BoxBX21--5 TB€10.90
Hetzner Storage BoxBX31--10 TB€20.80
Hetzner Storage BoxBX41--20 TB€40.60
Hetzner Object Storageमूल मूल्य, 1 TB स्टोरेज + 1 TB egress सहित--1 TB€6.49

12 · कैलकुलेटर: संसाधन और लागत

पूरी तरह ब्राउज़र में चलता है, बाहरी सेवाओं के बिना। धारणाएँ परिणाम के नीचे खुले रूप से सूचीबद्ध हैं।

धारणाएँ (मापी नहीं गईं, कृपया अपनी संख्याओं से जाँचें)
  • मेल वॉल्यूम = मेलबॉक्स × औसत आकार × (1 + वृद्धि)^वर्ष। डीडुप्लिकेशन या संपीड़न के लिए कोई कटौती नहीं (रूढ़िवादी अनुमान)।
  • PostgreSQL आकार = मेल वॉल्यूम का 3% (मेटाडेटा, फ़्लैग, फ़ुल-टेक्स्ट इंडेक्स), कम से कम 2 GB। वास्तविक मान SELECT pg_database_size('nextxch') से मिलता है।
  • PostgreSQL के लिए RAM = DB आकार का 25%, कम से कम 2 GB, 2/4/8/16/32/64/96/128 GB तक ऊपर पूर्णांकित। PostgreSQL के लिए डिस्क = 2 × DB आकार (डेटा, WAL, अतिरिक्त स्थान)।
  • nextxch प्रक्रिया: 1 GB + हर शुरू किए गए 1,000 मेलबॉक्स पर 1 GB (रिपॉज़िटरी में Kubernetes सीमा प्रति पॉड 1 GiB है)। etcd + Garage + OS: 1 GB। विटनेस: 2 GB RAM। ऑपरेटिंग सिस्टम: 20 GB डिस्क।
  • Garage replication_factor 3: हर ज़ोन (साइट या विटनेस) पूरा मेल वॉल्यूम संग्रहीत करता है।
  • सेटअप L: प्रति प्रदाता कम से कम 2 पॉड, प्रति 1,500 मेलबॉक्स 1 पॉड, 8 GB वाले वर्कर; डेटा VM पर PostgreSQL, etcd और Garage ज़ोन रहते हैं।
  • बैकअप आवश्यकता = 3 × DB आकार (2 पूर्ण बैकअप + WAL) + मेल वॉल्यूम; लक्ष्य: सबसे छोटा उपयुक्त Hetzner Storage Box।
  • लाइसेंस मूल्य इस वेबसाइट की वर्तमान मूल्य सूची से (स्तर मूल्य सभी मेलबॉक्स पर लागू होता है, वार्षिक मूल्य / 12)। Cloudflare USD में दिखाया गया है और यूरो योग में नहीं जोड़ा गया है।

13 · एक और प्रदाता जोड़ना (उदाहरण STRATO)

  1. ऑर्डर करने से पहले: क्या PTR सेट किया जा सकता है? STRATO: VPS के लिए रिवर्स DNS ग्राहक लॉगिन में डोमेन के अंतर्गत, पहले A/AAAA रिकॉर्ड मौजूद होना चाहिए, इसमें 24 h तक लगते हैं। पोर्ट 25: रूट सर्वर के लिए STRATO आउटबाउंड को अवरुद्ध बताता है, जिसे सपोर्ट के माध्यम से अनब्लॉक किया जाता है; VPS के लिए कोई नियम प्रलेखित नहीं है। STRATO कोई फेलओवर IP प्रदान नहीं करता। [31][32][30]
  2. नए होस्ट को उसके अपने WireGuard IP और Garage ज़ोन के साथ Ansible इन्वेंटरी में जोड़ें, सभी साइटों पर पीयर जोड़ें, प्लेबुक चलाएँ।
  3. etcd: सदस्य को etcdctl member add से जोड़ें और ETCD_INITIAL_CLUSTER_STATE=existing के साथ शुरू करें। चार डेटाबेस साइटों के साथ etcd सदस्यों की संख्या विषम रखें। [48]
  4. Patroni रेप्लिका के रूप में शुरू होता है और स्वचालित रूप से रेप्लिकेट करता है। Garage: नए ज़ोन के साथ garage layout assign, garage layout apply।
  5. DNS: IP को SPF में जोड़ें, समान प्राथमिकता वाला एक नया MX, नोड को DNS फेलओवर सूची (एजेंट या स्क्रिप्ट) में, नए MX नाम के लिए TLSA रिकॉर्ड, नए नोड का EHLO नाम और PTR, प्रमाणपत्र को इस नाम से विस्तारित करें। MTA-STS नीति नए MX को स्वचालित रूप से शामिल कर लेती है (स्वचालित MX सूची); फिर DNS एडवाइज़र से नया _mta-sts TXT मान प्रकाशित करें।
  6. साइट को पाइपलाइन मैट्रिक्स में जोड़ें और नई साइट के आउटेज का एक बार परीक्षण करें।

14 · सीमाएँ और कमियाँ (ईमानदारी से)

यह गाइड लिखते समय कोड में मिला। चार कमियाँ अब तक दूर हो चुकी हैं: कई MX नामों के साथ MTA-STS, प्रति नोड EHLO नाम, स्वचालित रूप से बनाया गया Object Lock बकेट और बिल्ट-इन DNS फेलओवर एजेंट। बाकी के लिए गाइड वर्णित तरीके से वैकल्पिक उपाय करती है।

कमीप्रभाववैकल्पिक उपाय
DNS फेलओवर एजेंट (बंद)अंतर्निहित, opt-in: हर नोड से हेल्थ चेक, केवल क्लस्टर लीडर Hetzner DNS या Cloudflare के माध्यम से A/AAAA बदलता है।अन्य DNS प्रदाता (उदा. deSEC): etcd लॉक के अंतर्गत स्क्रिप्ट (अध्याय 04)।
कई MX के साथ MTA-STS (हल किया गया)प्रति-डोमेन नीति सभी MX नामों को सूचीबद्ध करती है (DNS MX, सर्वर और नोड नाम, या वाइल्डकार्ड के साथ अपनी सूची); नीति id सूची के अनुसार बदलती है।हर MX परिवर्तन के बाद DNS सलाहकार से _mta-sts TXT मान प्रकाशित करें।
प्रति नोड EHLO नाम (हल किया गया)Smtp:HeloHostname या /admin/ में प्रति-नोड सेटिंग, प्रति नोड PTR जाँच के साथ; सर्वर होस्टनाम पूरे क्लस्टर के लिए रहता है।-
केवल एक S3 लक्ष्यअलग-अलग प्रदाताओं के दो S3 स्टोर में डुअल राइट नहीं; Object Lock सुविधाओं के लिए Object Lock वाला S3 आवश्यक है।तीन साइटों पर Garage और साथ में Object Lock बकेट में rclone copy।
Object Lock बकेट (बंद)WORM मोड कॉन्फ़िगर होने पर, सर्वर स्टार्टअप पर लुप्त bucket को Object Lock के साथ बनाता है। Object Lock के बिना मौजूदा bucket की चेतावनी और अलर्ट के रूप में सूचना दी जाती है, क्योंकि Object Lock केवल निर्माण के समय ही सक्षम किया जा सकता है। [18]GoBD आर्काइव में "Check S3 bucket" से कभी भी जाँचें।
कोई बिल्ट-इन बैकअप नहींसर्वर में डेटाबेस और ब्लॉब के लिए कोई बैकअप सेवा नहीं है।pgBackRest और rclone (अध्याय 09)।
कोई रीड रेप्लिका नहींसभी क्वेरी प्राइमरी पर जाती हैं; रीड रेप्लिका विकास योजना में केवल "अनुरोध पर" के रूप में सूचीबद्ध हैं।PostgreSQL प्राइमरी को वर्टिकल रूप से स्केल करें।
कोई मापा गया साइज़िंग बेंचमार्क नहींकैलकुलेटर में RAM और DB आकार अनुमान हैं।अपने स्वयं के मापों से ओवरराइड करें।

FAQ

क्या nextxch कई प्रदाताओं पर active-active चल सकता है?

हाँ। Cluster:Enabled=true और PostgreSQL के साथ कितने भी nextxch नोड LISTEN/NOTIFY, UNLOGGED टेबल और advisory lock के माध्यम से स्थिति साझा करते हैं। सभी नोड को एक ही PostgreSQL प्राइमरी और एक ही S3 ब्लॉब स्टोर तक पहुँचना चाहिए; प्रदाताओं के पार यह WireGuard नेटवर्क के माध्यम से होता है।

क्या मुझे लोड बैलेंसर या anycast की आवश्यकता है?

नहीं। इनबाउंड मेल को केवल समान प्राथमिकता वाले कई MX रिकॉर्ड चाहिए (RFC 5321 खंड 5.1)। IMAP, सबमिशन और HTTPS को छोटे TTL वाला एक नाम चाहिए, जिसके A रिकॉर्ड एक हेल्थ-चेक जॉब द्वारा बनाए रखे जाते हैं। Anycast/BGP संभव है, लेकिन जटिल है।

मुझे न्यूनतम कितनी साइटों की आवश्यकता है?

तीन वोट: दो पूर्ण साइटें और एक छोटा witness (etcd, Garage ज़ोन, फेलओवर चेकर), या तीन पूर्ण साइटें। केवल दो साइटों के साथ कोई भी पक्ष सुरक्षित रूप से नहीं बता सकता कि दूसरा विफल हुआ है या केवल लिंक टूटा है (split brain)।

क्या किसी प्रदाता के ऑफ़लाइन होने पर मेल खो जाती है?

प्रेषक कम से कम 4 से 5 दिनों तक पुनः प्रयास करते रहते हैं (RFC 5321 खंड 4.5.4.1)। Patroni synchronous_mode और Garage replication_factor 3 के साथ, 250 OK से स्वीकार किया गया संदेश पहले से ही दूसरी साइट पर संग्रहीत होता है।

15 · स्रोत

सभी बाहरी तथ्यों की जाँच 2 नवंबर 2026 को निम्नलिखित आधिकारिक स्रोतों से की गई। जिन बिंदुओं का सत्यापन नहीं हो सका, उन्हें पाठ में ऐसा ही चिह्नित किया गया है।

  1. RFC 5321 (SMTP): 4.1.4 EHLO, 4.5.4.1 retry, 5.1 MX: https://www.rfc-editor.org/rfc/rfc5321.html
  2. RFC 7208 (SPF): 4.6.4 DNS लुकअप सीमा, 5.6 ip4/ip6: https://www.rfc-editor.org/rfc/rfc7208.html
  3. RFC 6376 (DKIM): 3.1 सिलेक्टर: https://www.rfc-editor.org/rfc/rfc6376.html
  4. RFC 8461 (MTA-STS): 3.2, 4.1 mx पैटर्न: https://www.rfc-editor.org/rfc/rfc8461.html
  5. RFC 7672 (SMTP के लिए DANE): 3.1 TLSA 3 1 1: https://www.rfc-editor.org/rfc/rfc7672.html
  6. RFC 8314 (implicit TLS 465/993/995): https://www.rfc-editor.org/rfc/rfc8314.html
  7. RFC 2177 (IMAP IDLE, हर 29 मिनट में पुनः जारी): https://www.rfc-editor.org/rfc/rfc2177.html
  8. Google: ईमेल प्रेषक दिशानिर्देश (फ़ॉरवर्ड और रिवर्स DNS): https://support.google.com/a/answer/81126
  9. Hetzner Docs: Floating IPs: https://docs.hetzner.com/cloud/floating-ips/overview
  10. Hetzner Docs: IPv4 मूल्य निर्धारण: https://docs.hetzner.com/general/others/ipv4-pricing
  11. Hetzner Docs: Robot फेलओवर IP: https://docs.hetzner.com/robot/dedicated-server/ip/failover
  12. Hetzner Cloud: लागत-अनुकूलित प्लान: https://www.hetzner.com/cloud/cost-optimized/
  13. Hetzner Docs: क्लाउड सर्वर FAQ (पोर्ट 25/465): https://docs.hetzner.com/cloud/servers/faq/
  14. Hetzner Docs: PTR रिकॉर्ड: https://docs.hetzner.com/networking/dns/record-types/ptr-record/
  15. Hetzner Storage Box (मूल्य, प्रोटोकॉल): https://www.hetzner.com/storage/storage-box/
  16. Hetzner Object Storage (मूल्य): https://www.hetzner.com/storage/object-storage/
  17. Hetzner Docs: Object Storage अवलोकन: https://docs.hetzner.com/storage/object-storage/overview
  18. Hetzner Docs: Object Lock और प्रतिधारण: https://docs.hetzner.com/storage/object-storage/howto-protect-objects/protect-object-lock-retention/
  19. Hetzner Docs: Load Balancer FAQ: https://docs.hetzner.com/cloud/load-balancers/faq
  20. Hetzner Docs: Networks FAQ: https://docs.hetzner.com/cloud/networks/faq
  21. Hetzner Docs: DNS (Console और API): https://docs.hetzner.com/networking/dns/overview/
  22. netcup: VPS G12.5 मूल्य: https://www.netcup.com/de/server/vps
  23. netcup: Root Server G12.5 मूल्य: https://www.netcup.com/de/server/root-server
  24. netcup Helpcenter: IP पते और फेलओवर: https://www.netcup.com/en/helpcenter/documentation/server/ip
  25. netcup: सर्वर एक्सटेंशन (फेलओवर IP मूल्य): https://www.netcup.com/de/server/kvm-server-erweiterungen
  26. netcup Helpcenter: फ़ायरवॉल (नीति "netcup Mail block"): https://www.netcup.com/en/helpcenter/documentation/server/firewall
  27. netcup Helpcenter: नेटवर्क / rDNS: https://www.netcup.com/en/helpcenter/documentation/server/network-server
  28. netcup: Storage Space (NFS): https://www.netcup.com/en/server/storage-space
  29. netcup Helpcenter: DNS API: https://www.netcup.com/en/helpcenter/documentation/domain/our-api
  30. STRATO: VPS Linux मूल्य: https://www.strato.de/server/linux-vserver/
  31. STRATO FAQ: VPS के लिए Reverse DNS: https://www.strato.de/faq/server/wie-kann-ich-reverse-dns-fuer-meinen-vps-vds-einrichten/
  32. STRATO FAQ: रूट सर्वर नेटवर्क फ़ायरवॉल (पोर्ट 25): https://www.strato.de/faq/server/wie-stelle-ich-die-netzwerk-firewall-bei-meinem-windows-root-server-ein/
  33. Cloudflare: Load Balancing ऐड-ऑन मूल्य: https://www.cloudflare.com/plans/network-cdn/
  34. Cloudflare Docs: Load Balancing सीमाएँ (हेल्थ चेक अंतराल): https://developers.cloudflare.com/load-balancing/reference/limitations/
  35. Cloudflare Docs: Load Balancing DNS रिकॉर्ड (MX/SRV केवल Enterprise): https://developers.cloudflare.com/load-balancing/load-balancers/dns-records/
  36. Cloudflare Docs: प्रॉक्सी मोड (DNS-only): https://developers.cloudflare.com/load-balancing/understand-basics/proxy-modes/
  37. Cloudflare Docs: प्रति प्लान Spectrum प्रोटोकॉल: https://developers.cloudflare.com/spectrum/protocols-per-plan/
  38. Cloudflare Docs: DNS TTL: https://developers.cloudflare.com/dns/manage-dns-records/reference/ttl/
  39. Cloudflare API: DNS रिकॉर्ड संपादित करें: https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/
  40. deSEC: डोमेन और न्यूनतम TTL: https://desec.readthedocs.io/en/latest/dns/domains.html
  41. Akamai/Linode: IP फेलओवर (BGP, समान डेटा सेंटर): https://techdocs.akamai.com/cloud-computing/docs/configure-failover-on-a-compute-instance
  42. Akamai: NodeBalancer मूल्य: https://www.akamai.com/cloud/pricing/nodebalancers
  43. Vultr: BGP आवश्यकताएँ: https://docs.vultr.com/configuring-bgp-on-vultr
  44. RIPE NCC: AS नंबर का अनुरोध: https://www.ripe.net/manage-ips-and-asns/as-numbers/request-an-as-number/
  45. Proxmox VE: Cluster Manager (corosync लेटेंसी, QDevice): https://pve.proxmox.com/wiki/Cluster_Manager
  46. Proxmox Backup Server: डेटास्टोर (S3 सहित): https://pbs.proxmox.com/docs/storage.html
  47. WireGuard: https://www.wireguard.com/
  48. k3s: एम्बेडेड etcd के साथ HA: https://docs.k3s.io/datastore/ha-embedded
  49. k3s: वितरित हाइब्रिड या मल्टीक्लाउड क्लस्टर: https://docs.k3s.io/networking/distributed-multicloud
  50. etcd: ट्यूनिंग (heartbeat, election timeout): https://etcd.io/docs/v3.5/tuning/
  51. Talos Linux: KubeSpan: https://docs.siderolabs.com/talos/v1.11/networking/kubespan
  52. CloudNativePG: replica clusters: https://cloudnative-pg.io/docs/current/replica_cluster
  53. Patroni: डायनामिक कॉन्फ़िगरेशन (ttl, loop_wait, retry_timeout): https://patroni.readthedocs.io/en/latest/dynamic_configuration.html
  54. Patroni: प्रतिकृति मोड (synchronous_mode, strict): https://patroni.readthedocs.io/en/latest/replication_modes.html
  55. Patroni: DCS failsafe mode: https://patroni.readthedocs.io/en/latest/dcs_failsafe_mode.html
  56. PostgreSQL 18: सिंक्रोनस रेप्लिकेशन: https://www.postgresql.org/docs/current/warm-standby.html#SYNCHRONOUS-REPLICATION
  57. PostgreSQL 18: shared_buffers: https://www.postgresql.org/docs/current/runtime-config-resource.html
  58. pgBackRest (S3 और SFTP रिपॉज़िटरी): https://pgbackrest.org/
  59. CISA: डेटा बैकअप विकल्प (3-2-1): https://www.cisa.gov/sites/default/files/publications/data_backup_options.pdf
  60. Garage: प्रोजेक्ट पेज (मल्टी-डेटासेंटर, 200 ms / 50 Mbps): https://garagehq.deuxfleurs.fr/
  61. Garage: कॉन्फ़िगरेशन (replication_factor, कोरम): https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/
  62. Garage: S3 संगतता (कोई Object Lock नहीं, कोई वर्शनिंग नहीं): https://garagehq.deuxfleurs.fr/documentation/reference-manual/s3-compatibility/
  63. Npgsql: फेलओवर और लोड बैलेंसिंग (मल्टी-होस्ट): https://www.npgsql.org/doc/failover-and-load-balancing.html
  64. Mermaid 12.2.0 (MIT), इस साइट पर vendored: https://www.npmjs.com/package/mermaid