- छोटा और तेज़ HTTPS सर्वर zeroserve वेबसाइट tarball लेकर उसे HTTP/2 और TLS 1.3 के साथ सर्व करता है, और tarball के अंदर मौजूद eBPF प्रोग्रामों को user-space sandbox middleware के रूप में हर request पर चलाता है
- कॉन्फ़िगरेशन फ़ाइलों के बिना eBPF प्रोग्राम request-स्तर पर routing, headers, authentication, rate limiting और proxying तय करते हैं, जिससे nginx·Caddy की declarative settings और अलग scripting layer एक में जुड़ जाती हैं
- साइट को एकल tar फ़ाइल के रूप में index किया जाता है और डिस्क पर extract नहीं किया जाता; tarball बदलकर और
SIGHUP भेजकर साइट, scripts और TLS सामग्री को बिना connection loss के atomically बदला जा सकता है
- single-core HTTPS benchmark में zeroserve ने छोटे static files पर 36,681 req/s, 10ms eBPF dynamic JSON पर 46,945 req/s, और छोटे proxy पर 26,486 req/s दर्ज किए, लेकिन 100KB proxy में nginx 5,882 req/s के साथ आगे रहा
- zeroserve का लक्ष्य nginx और Caddy का विकल्प बनना है, जो single tarball deployment, programmable configuration, user-space eBPF और modern TLS को जोड़ता है, लेकिन बड़े proxy responses के लिए nginx अधिक उपयुक्त है
अवलोकन
- zeroserve एक छोटा, तेज़, बिना-कॉन्फ़िगरेशन वाला HTTPS सर्वर है जो एक वेबसाइट tarball को HTTP/2 और TLS 1.3 के साथ सर्व करता है
- tarball के अंदर रखे eBPF प्रोग्राम हर request पर user-space sandbox middleware की तरह चलते हैं, और request rewriting, authentication, rate limiting, तथा backend reverse proxying संभाल सकते हैं
- single-core आधार पर छोटे/बड़े static files, scripted middleware और छोटे-response proxying समेत अधिकतर workloads में nginx से बेहतर performance देने के लक्ष्य वाला सर्वर है
- eBPF scripts native code में JIT compile होते हैं और user space में sandbox किए जाते हैं, और इन्हें हर request पर चलाने लायक कम overhead के लिए डिज़ाइन किया गया है
- network और disk operations monoio runtime के माध्यम से
io_uring पर submit किए जाते हैं
- TLS 1.3, HTTP/2, Encrypted Client Hello, SNI certificate selection, और JA4 fingerprinting का समर्थन
- पूरी साइट और TLS सामग्री एक ही tarball से दी जाती है, और
SIGHUP से hot reload संभव है
कॉन्फ़िगरेशन मॉडल: प्रोग्राम ही कॉन्फ़िगरेशन है
- zeroserve का लक्ष्य nginx और Caddy का विकल्प बनना है, और इसका मुख्य design choice इसका configuration model है
- nginx और Caddy
location blocks, rewrite rules, map directives, try_files जैसी declarative configuration languages देते हैं, और सीमा आने पर Lua या Caddy plugins जैसे optional scripting runtimes जोड़े जाते हैं
- इस संरचना में behavior अपने control flow वाली directive layer और request lifecycle के विशेष बिंदुओं पर चलने वाली script layer में बंट जाता है
- zeroserve में कोई configuration file नहीं है; एक eBPF प्रोग्राम सभी requests को देखकर routing, headers, authentication, rate limiting और proxying तय करता है
एकल tarball को ज्यों का त्यों सर्व करना
- पूरी साइट एक
tar फ़ाइल होती है, और zeroserve load के समय path -> byte-range map बनाकर tarball पर सीधे byte-range reads करके files सर्व करता है
- कोई भी file डिस्क पर extract नहीं होती, इसलिए साइट केवल एक ही फ़ाइल के भीतर मौजूद रहती है, और कोई गलत
location rule document root को expose नहीं कर सकता
- deployment एक single file की atomic replacement से होता है; नया version deploy करने के लिए tarball बदलें और फिर
SIGHUP भेजें
- directory packaging और run command का फ़ॉर्मेट इस प्रकार है
zeroserve --pack ./public > site.tar
zeroserve --addr 0.0.0.0:8080 site.tar
- hot reload command का फ़ॉर्मेट इस प्रकार है
killall -SIGHUP zeroserve
- reload उसी process के भीतर साइट, scripts और TLS सामग्री को atomically बदलता है और बिना connection loss के काम करता है
- हर instance एक single-threaded event loop है; एक process के स्तर पर यह सीमा है, लेकिन जब scaling unit “अधिक processes” हों, तब यह मॉडल उपयुक्त बताया गया है
user-space eBPF scripting
.zeroserve/scripts/ के नीचे रखी सभी .c files packaging के समय clang और llc से eBPF objects में compile की जाती हैं और हर request पर चलाई जाती हैं
- eBPF kernel BPF subsystem या
CAP_BPF के बिना, सामान्य unprivileged process के भीतर async-ebpf runtime पर user space में चलाया जाता है
- async-ebpf में uBPF शामिल है, जो bytecode को native x86-64 machine code में JIT compile करता है
- pointer cage JIT-compiled code की सभी memory accesses को program-specific arena तक mask करता है, ताकि गलत access script memory के भीतर ही सीमित रहे
- scripts सीधे zeroserve के single event loop में चलती हैं, और धीमी scripts दूसरे connections को रोक न दें, इसके लिए timer JIT-compiled native code को बीच execution में interrupt कर सकता है और control event loop को लौटा सकता है
- programming model scripts की एक chain है जो filename sorting order में चलती है, और scripts request-स्तर के metadata map को साझा करती हैं
- यदि script
zs_respond या zs_reverse_proxy को call करती है, तो chain short-circuit होकर समाप्त हो जाती है
zs.response.header.* के नीचे की keys सभी responses के headers बनती हैं, और बाकी keys एक छोटे template pass में output समय पर replace की जाती हैं, जैसे HTML फ़ाइल में <zs-meta>visitor</zs-meta> placeholder
- helper surface request method, path, query, headers, peer address पढ़ने, URI rewrite करने, और headers set/delete करने का समर्थन देता है
- cryptography और encoding helpers में SHA-256, HMAC-SHA256, base64, hex, और
getrandom शामिल हैं
- JSON helpers request body parsing, document tree creation/modification, और
zs_json_respond response का समर्थन देते हैं
- rate limiting peer IP या API key जैसी arbitrary keys पर आधारित token bucket का समर्थन करती है, और state hot reload के बाद भी बनी रहती है
- AWS SigV4 helper S3 और अन्य AWS services के साथ संचार के लिए signed
Authorization header और presigned URL का समर्थन देता है
- OIDC login Authorization Code + PKCE आधारित relying-party flow देता है, और पूरा login session sealed XChaCha20-Poly1305 cookie में रखता है, जिससे server stateless रहता है और static site को “Google से login” के पीछे रखा जा सकता है
- dynamic endpoints में script किसी विशेष path पर सीधे response देती है; उदाहरण में
/health request पर application/json header और {"status":"ok"} body लौटाई जाती है
- हर script default 256KB memory limit के भीतर चलती है, और runtime लंबे समय तक चलने वाली scripts को executor में time-slice करता है और runaway scripts को throttle करता है
- scripts
zs_call के ज़रिए एक-दूसरे को call कर सकती हैं, और call depth सीमित होती है
- infinite loop में फँसी script केवल अपनी request को delay करती है; preemption timer उसे interrupt कर देता है, इसलिए server दूसरी requests संभालता रहता है
- TLS layer केवल TLS 1.3 पर आधारित है और BoringSSL से terminate होती है
- Encrypted Client Hello वास्तविक SNI को plaintext में दिखने से रोकता है, और directory-based SNI certificate selection तथा scripts को exposed JA4 client fingerprinting देता है
- transparent ECH relay mode decrypt न की जा सकने वाली handshakes को byte-for-byte वास्तविक upstream तक forward करता है, ताकि protected names public name के पीछे मिलाए जा सकें
प्रदर्शन
-
benchmark की शर्तें
- zeroserve, nginx 1.26, और Caddy 2.11 की तुलना 8-core Ryzen 7 3700X पर एक ही content और एक ही self-signed certificate के साथ HTTPS serving में की गई
- क्योंकि zeroserve instance design के अनुसार single-threaded है, इसलिए comparison baseline per-core performance है
- सभी servers को
taskset से एक CPU पर pin किया गया; nginx में worker_processes 1, Caddy में GOMAXPROCS=1, और zeroserve में उसका मौजूदा single-threaded model इस्तेमाल हुआ
- load दूसरे cores से
wrk -t4 -c100 द्वारा generate किया गया, और 10-second run के 3 प्रयासों का median लिया गया
wrk HTTP/1.1 इस्तेमाल करता है, इसलिए आँकड़े TLS 1.3 के ऊपर HTTP/1.1 के हैं, और लंबे keep-alive connections पर handshake cost amortize होने के बाद already-open HTTPS connections की steady-state cost दर्शाते हैं
-
छोटा static file 174B
| सर्वर |
req/s |
p99 |
| zeroserve |
36,681 |
5.4 ms |
| nginx |
31,226 |
7.8 ms |
| Caddy |
12,830 |
22 ms |
- zeroserve ने single core पर nginx से लगभग 17% तेज़ी से छोटे files serve किए, और tail latency भी कम रही
- HTML pages, छोटे JSON, CSS जैसी static site की बुनियादी स्थितियाँ zeroserve tuning का लक्ष्य हैं
-
बड़ा static file 100KB
| सर्वर |
req/s |
throughput |
p99 |
| zeroserve |
8,000 |
782 MB/s |
22 ms |
| nginx |
7,600 |
773 MB/s |
28 ms |
| Caddy |
6,084 |
590 MB/s |
44 ms |
- तीनों servers के परिणाम काफ़ी पास थे, और zeroserve single core पर लगभग 780 MB/s के साथ थोड़ा आगे रहा
- बड़े files के लिए nginx की
sendfile() बढ़त TLS के नीचे लागू नहीं होती, क्योंकि bytes को user space में encrypt करना पड़ता है, इसलिए तीनों servers encryption और write loop से बंधे रहते हैं
- तीनों servers में kernel TLS बंद होने पर zeroserve का
io_uring read/write path थोड़ा तेज़ निकला
eBPF बनाम Lua
- scripting comparison के लिए nginx + LuaJIT
ngx_http_lua_module लिया गया, जो web server के अंदर तेज़ code चलाने का आम तरीका है
- zeroserve default रूप से script preemption timer को हर 2ms पर सेट करता है; छोटी interval problem scripts को जल्दी throttle करती है, लेकिन सामान्य scripts पर भी overhead डालती है
- default 2ms पर पूरी तरह dynamic response में eBPF लगभग 32k req/s देता है, जो nginx Lua के 41k req/s से कम है
--preempt-timer-interval-ms को 10 करने पर scripting throughput लगभग 40% लौट आता है और परिणाम पलट जाता है
-
प्रति-request header injection middleware
| इंजन |
req/s |
p99 |
| zeroserve eBPF 10ms |
43,709 |
5.1 ms |
| zeroserve eBPF 2ms default |
31,334 |
6.7 ms |
nginx Lua header_filter |
28,653 |
8.4 ms |
- उस middleware case में जहाँ script चलती है लेकिन static file serving जारी रहती है, 10ms eBPF nginx Lua से लगभग 50% अधिक throughput और कम tail latency देता है
-
पूरी तरह dynamic JSON response
| इंजन |
req/s |
p99 |
| zeroserve eBPF 10ms |
46,945 |
4.5 ms |
nginx Lua content_by_lua |
41,231 |
6.4 ms |
| zeroserve eBPF 2ms default |
32,393 |
6.7 ms |
- 10ms interval पर tuned eBPF ने पूरी तरह synthesized responses में भी nginx के
content_by_lua से अधिक throughput दर्ज किया
- दोनों engines native code में compile होते हैं; LuaJIT tracing JIT है, जबकि async-ebpf uBPF के माध्यम से eBPF को JIT compile करता है
- जहाँ TLS encryption एक common request cost है, वहाँ tuned eBPF path throughput में आगे रहा
- 2ms default पर eBPF middleware बढ़त बनाए रखता है, लेकिन synthesized response में बढ़त खो देता है, इसलिए production scripts के लिए 10ms की सिफ़ारिश की गई है
reverse proxy के रूप में उपयोग
- zeroserve script में
zs_reverse_proxy("http://127.0.0.1:9000") call करके backend की ओर proxy कर सकता है
- upstream connection pool प्रति backend अधिकतम 128 connections और 30-second idle reuse का समर्थन करता है
- निष्पक्ष तुलना के लिए nginx में, क्योंकि वह default रूप से हर request पर upstream connection बंद कर देता है,
keepalive 128, proxy_http_version 1.1, और खाली Connection header स्पष्ट रूप से इस्तेमाल किए गए
- Caddy ने अपने default behavior के अनुसार connection reuse किया
- हर proxy ने single core पर TLS terminate किया और साझा plaintext backend को forward किया; backend एक अलग 2-core server था जो स्वयं 100k req/s बनाए रखता था, ताकि केवल proxy overhead मापा जा सके
-
छोटा 174B response proxy
| प्रॉक्सी |
req/s |
p50 |
p99 |
| zeroserve |
26,486 |
3.3 ms |
8 ms |
| nginx |
21,761 |
4.2 ms |
10.5 ms |
| Caddy |
7,683 |
10.3 ms |
33 ms |
- zeroserve का pooled
io_uring proxy nginx से लगभग 22% आगे रहा और Caddy की तुलना में लगभग 3.4x throughput दर्ज किया
- API calls, छोटे JSON, app server HTML जैसी सामान्य proxy workloads में zeroserve ने TLS termination और backend forwarding तेज़ी से किया
-
100KB response proxy
| प्रॉक्सी |
req/s |
throughput |
| nginx |
5,882 |
585 MB/s |
| Caddy |
4,285 |
406 MB/s |
| zeroserve |
3,631 |
359 MB/s |
- जब proxy body बड़ी होती है, तो nginx की buffering bytes को अधिक कुशलता से आगे बढ़ाती है और वह आगे निकलता है; Caddy बीच में और zeroserve पीछे रहता है
- बड़े proxy responses के लिए nginx बेहतर tool है, जबकि छोटे और अधिक संख्या वाले responses में zeroserve तेज़ है
मेमोरी
- idle स्थिति में एक single zeroserve instance लगभग 15MB PSS उपयोग करता है, जो nginx के लगभग 6MB से अधिक लेकिन Caddy के लगभग 60MB से कम है
- यह महत्वपूर्ण है कि execution unit पूरा process है; जब हर core पर copies चलाई जाती हैं, तो वही binary map होकर code pages साझा करती है
- अतिरिक्त processes अपनी working set के अलावा कम अतिरिक्त memory जोड़ते हैं
सार्वजनिक उपलब्धता
- zeroserve GitHub पर open source के रूप में उपलब्ध कराया गया प्रोजेक्ट है
1 टिप्पणियां
Hacker News टिप्पणियाँ
TechEmpower वेब सर्वर बेंचमार्क के गायब हो जाने से लगता है कि ऐसे नए प्रोजेक्ट्स के लिए खुद को साबित करने के मौके कम हो गए हैं
सुधार: लगता है मैं पीछे रह गया था, और आजकल चलन में शायद https://www.http-arena.com/leaderboard/ है। शुभकामनाएँ
हाँ, वैसे भी यह पहले से बहुत बार नहीं चलता था, और राउंड रिकॉर्ड देखें तो साल में एक बार से भी कम चला है
यह देखना अच्छा है कि LLM की वजह से इस तरह की कोशिशों को अब अपेक्षाकृत सस्ते और तेज़ तरीके से एक्सप्लोर किया जा सकता है
लेकिन यहाँ मेरी एक और प्रतिक्रिया यह थी कि nginx खुद काफ़ी प्रभावशाली है। एक और बात जो ध्यान खींचती है, वह यह दावा था कि यह प्रोजेक्ट nginx और Caddy का विकल्प है और यह configuration approach पर दांव लगा रहा है
nginx और Caddy declarative configuration language देते हैं, और जब उसकी सीमा आ जाती है तो उसके बगल में Lua या Caddy plugin जैसे scripting runtime जोड़ दिए जाते हैं, इसलिए व्यवहार दो परतों में बँट जाता है
लेकिन मुझे लगता है कि यह दांव ग़लत है। लोग बहुत पहले से code की तुलना में config को पसंद करते आए हैं, और built-in features ही अक्सर काफ़ी होते हैं, इसलिए C code लिखने की ज़रूरत नहीं पड़ती
हर configuration file format शायद शुरुआत में सरल ही होता है। YAML भी मूल रूप से काफ़ी ठीक-ठाक था, लेकिन बाद में लोगों ने anchors और aliases के साथ और जटिल चीज़ें माँगनी शुरू कर दीं
GitLab के पास भी conditionals और variables जैसे अपने फ़ॉर्मेट हैं, और वे कुछ खास जगहों पर ही काम करने वाले hack जैसे लगते हैं। Apache भी XML-आधारित config format के साथ कुछ ऐसी ही दिशा में गया था
आख़िरकार configuration management के लिए ढेर सारी custom programming languages बन जाती हैं। एंटरप्राइज़ माहौल में लोग इन्हें सीधे एडिट भी नहीं करते, बल्कि Ansible workflows को script करके दूर से ऑपरेशन करते हैं
इससे बेहतर होता कि सर्वर में Lua या Python जैसा interpreter एम्बेड कर दिया जाता और उसी से configuration manage होती; यह custom config files को प्रोग्राम से बदलने की तुलना में ज़्यादा सीधा होता
बेशक कोई कह सकता है कि custom approaches सामान्य भाषाओं से ज़्यादा domain-optimized हैं, लेकिन ऐसी दलीलें अक्सर सिर्फ़ उन toy examples की छोटी-सी सीमा में फिट बैठती हैं जहाँ शायद उस पूरे तंत्र की ज़रूरत ही नहीं थी
Windows INI files याद हैं? वे अच्छे दिन थे, जब code अलग था और data अलग
और भी सरल रूप में, Kubernetes cluster के सारे Ingress manifests पढ़कर pack को दोबारा बनाया जा सकता है
बात यह है कि tools और config के बीच का interface भी बस एक और API है, और system operators पहले से ही उच्च-स्तरीय constructs के ज़रिए system state को बयान कर रहे हैं; config बनाने वाले वास्तविक bytes तो बस उसका परिणाम हैं
AI के नज़रिए से शायद यह तरीका ज़्यादा संभालने लायक हो। AI दोनों तरह की चीज़ें संभाल सकता है, इसलिए इस बदलाव को साफ़ तौर पर अच्छा विचार मानने में शायद अभी समय लगेगा
आइडिया पसंद आया
लेकिन अगर eBPF directory में
.cfiles की जगह.rsfiles रखी जा सकें, तो ज़्यादा भरोसा होगा। वैसे भी यह पहले से एक Rust project हैऔर न जाने क्यों, मैं kernel-accelerated web server की उम्मीद कर रहा था। अगर eBPF के साथ यह सुरक्षित तरीके से हो सके, तो वाकई कमाल होगा
और यह single-threaded है? Linux पर fork करके incoming connection queue को साझा करना लगभग मामूली काम है, और Rust में भी यह कुछ लाइनों में हो जाएगा। SO_REUSEPORT इस्तेमाल करें, बाकी kernel संभाल लेगा
वैसे अगर io_uring को आगे बढ़ाना है, तो kTLS को भी साथ में बढ़ाना चाहिए। अगर handshake के बाद user-space SSL processing से बचा जा सके, तो design काफ़ी सरल हो जाती है
अब तक मैं ऐसे कामों के लिए nftables इस्तेमाल करता रहा हूँ, इसलिए इसकी सीधे ज़रूरत नहीं पड़ी
बहुत बढ़िया। जिज्ञासा है कि क्या इसे XDP program या socket map से जुड़ने वाले programs जैसे दूसरे BPF program types के साथ मिलाकर, L7 HTTP features को और नीचे की लेयर में इंटीग्रेट किया जा सकता है
विचार अच्छा है, लेकिन पता नहीं static files पर फ़ोकस करना सही है या नहीं। आजकल उस काम के लिए नए सर्वर कम ही खड़े किए जाते हैं
इसलिए यह मुझे ऐसा लगता है जैसे मेरे लिए ही बनाया गया हो, हालाँकि मैं मानता हूँ कि मैं आम यूज़र नहीं हूँ
अच्छा लग रहा है और फीचर्स भी ठीक लगते हैं। लेकिन कहीं न कहीं यह बहुत कृत्रिम सा महसूस होता है, इसलिए मन तुरंत नहीं जुड़ता
यह पता नहीं चलता कि metrics नकली हैं या नहीं, convenience functions वास्तव में काम करते हैं या नहीं, और क्या सही तरह की hardening हुई है
vibe coding से बनाया गया हो और README auto-generate किया गया हो, यह तक मैं मान सकता हूँ। लेकिन अगर announcement blog post भी AI ने बनाई है, तो मेरे पास यह परखने का कोई आधार नहीं बचता कि software quality को लेकर इसकी समझ मेरी समझ जैसी है या नहीं
अजीब दुनिया है। कुछ साल पहले अगर AI का खुलासा किए बिना यह घोषणा होती, तो मैं बिना शक के इसे स्वीकार कर लेता; लेकिन अब जब कोई शानदार README और भरोसेमंद दिखने वाले command-line parameters दिखते हैं, तो तुरंत शक होता है कि README ने hallucinate किया है और शायद वास्तव में वे options हों ही नहीं
zeroserve खुद बनाते समय मैं AI की मदद काफी लेता हूँ, लेकिन AI का output मैं खुद जाँचता हूँ और ज़िम्मेदारी भी मेरी ही होती है
single core पर छोटी files serve करने में zeroserve, nginx से लगभग 17% तेज है और tail latency भी कम है। HTML page, छोटा JSON, CSS जैसे मामलों में zeroserve बेहतर बैठता है
100KB की बड़ी static file पर zeroserve 8,000 req/s, 782 MB/s, p99 22ms देता है, जबकि nginx 7,600 req/s, 773 MB/s, p99 28ms और Caddy 6,084 req/s, 590 MB/s, p99 44ms देता है
फिर भी मैं ऐसे नए प्रोजेक्ट की बजाय audit किए गए, production में परखे गए, और hardened पुराने प्रोजेक्ट को चुनूँगा। improvement इतना बड़ा नहीं है कि उसके लिए risk लिया जाए
मैंने तय किया है कि जितना हो सके पुराने दौर में ही रहूँ। समझदार लोग software जारी करते हैं, और समझदार लोग ही उसका maintenance करते हैं। उन्हें AI की ज़रूरत नहीं है। वही मेरी niche है
हो सकता है हम खत्म हो जाएँ, लेकिन फिर भी मुझे वही बेहतर लगता है। बस शर्त यह है कि वे समझदार लोग documentation भी लिखें। बहुत से समझदार लोग documentation लिखना पसंद नहीं करते
बहुत पहले मैंने तय कर लिया था कि documentation के बिना software, चाहे कितना भी शानदार हो, मेरे समय के लायक नहीं है। यह ज़्यादातर application side की बात है; Linux documentation मैंने लगभग कभी नहीं देखी, हालांकि दूसरे लोग कहते हैं कि वह इतनी बुरी भी नहीं है, तो पता नहीं
दिलचस्प नया concept है और मुझे पसंद आया
असली सवाल developer commitment और community का है। Caddy और Nginx के लोग लगातार अपने product को support करते आए हैं, और इस प्रोजेक्ट को भी बहुत फोकस और ध्यान की ज़रूरत होगी
tarball क्यों?
disk पर कुछ भी extract नहीं किया जाता। क्योंकि पूरी site उसी एक file में बंद है, इसलिए किसी गलत location rule से expose होने वाला document root होता ही नहीं, और deploy भी एक single atomic file replacement बन जाता है
हालांकि यह व्याख्या भी LLM-शैली का justification हो सकती है। लेख में जगह-जगह “the right shape” या “the surface is broad” जैसे वाक्य बिखरे हुए हैं