Course-এর সূচি

1.13

Web Server ও App Server: Nginx, Apache, Caddy ও অন্যান্য

Web Server আসলে কী করে, Apache ও Nginx ভেতরে কীভাবে কাজ করে, App Server ও তাদের Interface, এবং Production-এ এরা কীভাবে একসঙ্গে বসে।

42 মিনিট পড়া + Animation

টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।

এই Lesson শেষে তুমি পারবে

  • প্রতিটি Request নিয়ে Web Server ঠিক কী করে তা বলতে, এবং Web Server, Application Server, Reverse Proxy ও Load Balancer আলাদা করে চিনতে, এমনকি একটি Program চারটি ভূমিকাই পালন করলেও।
  • Apache ভেতরে কীভাবে কাজ করে (Virtual Host, Module, .htaccess, prefork, worker ও event MPM) এবং Nginx ভেতরে কীভাবে কাজ করে (Master ও Worker, Event Loop, server ও location Block) তা ব্যাখ্যা করতে।
  • আসল Nginx Config পড়তে ও লিখতে: Static File, try_files, Reverse Proxy Header, Load Balancing, Caching, Rate Limiting, TLS, এবং Graceful Reload।
  • Caddy, IIS, LiteSpeed, Tomcat, HAProxy, Envoy, Traefik ও Varnish-কে Request-এর পথে ঠিক জায়গায় বসাতে।
  • Application Server কেন দরকার এবং Web Server তাদের সঙ্গে কীভাবে কথা বলে তা ব্যাখ্যা করতে: CGI, FastCGI ও PHP-FPM, WSGI ও ASGI, Rack, Servlet, Kestrel, Node.js।
  • Log দেখে 502, 504, 499, 503, 413, 403 ও Redirect Loop নির্ণয় করতে, এবং System Design Interview-তে একটি Production Stack বর্ণনা করতে।

#Web Server আসলে কী করে

Server আসলে কী? Lesson-এ দেখেছ, Server হলো এমন একটি Program যা একটি Port-এ Listen করে আর Client-কে উত্তর দেয়। Web Server সেই ধারণারই একটি Protocol-এর জন্য বিশেষ রূপ: এটি এমন Software যা HTTP-তে কথা বলে। প্রতিটি Request একই পাঁচটি ধাপ পেরোয়:

  1. Connection গ্রহণ। Server Port 80 (HTTP) ও 443 (HTTPS)-এ Listen করছে। অপারেটিং সিস্টেম TCP Handshake শেষ করে নতুন Connection-টি তার হাতে দেয়। 443-এ Server এরপর নিজের Certificate ও Private Key দিয়ে TLS Handshake করে।
  2. Request পার্স করা। সে Request Line ও Header পড়ে (GET /images/logo.png HTTP/1.1, Host: example.com, …), আর Body থাকলে সেটাও। ভুলভাবে গঠিত Request এখানেই 400 দিয়ে বাতিল হয়।
  3. কী করবে ঠিক করা। Configuration দেখে সে Host Header মিলিয়ে একটি Site আর Path মিলিয়ে একটি Rule বেছে নেয়, তারপর একটি কাজ ঠিক করে: Disk থেকে File পাঠানো, Request-টি Application-এর হাতে দেওয়া, Redirect করা (301/302), বা প্রত্যাখ্যান করা (403, 404, 413, 429…)।
  4. Response পাঠানো। Status Line, Header (Content-Type, Content-Length, Cache-Control…) আর Body, দরকার হলে Compress করে। Keep-alive থাকলে Connection পরের Request-এর জন্য খোলা থাকে।
  5. Log করা। Access Log-এ একটি লাইন (কে, কী চাইল, Status, আকার, কত সময়), আর কিছু ভুল হলে Error Log-এ একটি লাইন।

সব আকর্ষণীয় পার্থক্য তৃতীয় ধাপে। Content দুই ধরনের:

  • Static Content হলো এমন File যা আগে থেকেই আছে এবং সবার জন্য একই: আগে থেকে বানানো HTML Page, CSS, JavaScript, Image, Font, Video, Download। Web Server এটা Disk (বা Memory) থেকে পড়ে পাঠিয়ে দেয়। এটা খুব দ্রুত, কোনো Programming Language-ই লাগে না।
  • Dynamic Content প্রতিটি Request-এর জন্য Code দিয়ে তৈরি হয়: তোমার Feed, Shopping Cart, Search Result, JSON-এ একটি API Response। Web Server একা এটা বানাতে পারে না। সে Request-টি PHP, Python, Ruby, Java, C#, JavaScript বা Go-তে লেখা একটি Application-এর কাছে পাঠায়, যে সাধারণত Database-এর সঙ্গে কথা বলে, তারপর উত্তরটি ফেরত পৌঁছে দেয়।

নিচের পরিস্থিতিগুলো চালাও: একটি Static File-এর Request, একটি Dynamic Page-এর Request, আর প্রতিদিন যেভাবে এগুলো ব্যর্থ হয়।

Web Server-এর ভেতর দিয়ে একটি Request

App Server-এর সামনে Nginx। একটি পরিস্থিতি বেছে নাও: Static File, Dynamic Page, বা প্রতিদিনের কোনো ব্যর্থতা।

পরিস্থিতি

আগে থেকেই থাকা একটি File: Web Server একাই উত্তর দেয়, App-কে ডাকতেই হয় না।

Browser203.0.113.7
NginxWeb Server :443
Disk/var/www/public
App ServerGunicorn :8000
GET /assets/logo.png
ধাপ 1 / 6
1

Browser-এর Nginx-এর সঙ্গে আগে থেকেই TCP ও TLS Connection আছে (না থাকলে খোলে)। সে একটি Logo চেয়ে Request পাঠায়।

#চারটি ভূমিকা, প্রায়ই একটি Program

Application-এর সামনের Box-গুলোর জন্য মানুষ চারটি শব্দ ব্যবহার করে, আর একই Software প্রায়ই একসঙ্গে কয়েকটি ভূমিকা পালন করে:

ভূমিকাকাজসাধারণ Software
Web ServerHTTP-তে কথা বলে, Static File পাঠায়, নিয়ম প্রয়োগ করে (Redirect, Header, Access)Nginx, Apache, Caddy, IIS, LiteSpeed
Application Serverএকটি Language Runtime ধরে রাখে এবং Dynamic Request-এর জন্য তোমার Code চালায়PHP-FPM, Gunicorn, Uvicorn, Puma, Tomcat, Kestrel, Node.js
Reverse Proxyপেছনের Server-দের হয়ে Request নিয়ে তাদের কাছে পাঠায়; TLS, Caching, Header যোগ করেNginx, HAProxy, Envoy, Traefik, Caddy, mod_proxy-সহ Apache
Load Balancerএমন Reverse Proxy যা একাধিক একই রকম Backend-এ Request ভাগ করে এবং অসুস্থগুলো এড়িয়ে চলেHAProxy, Nginx, Envoy, AWS ALB/NLB, Google Cloud Load Balancing

একটি Nginx Process একসঙ্গে Disk থেকে /static/ পাঠাতে পারে (Web Server), /api/ তিনটি App Server-এ ভাগ করে পাঠাতে পারে (Reverse Proxy ও Load Balancer) এবং উত্তরগুলো Cache করতে পারে। আবার Tomcat-কে Application Server বলা হয়, কিন্তু সে দিব্যি HTTP-তে কথা বলে, তাই সে Web Server-ও। তোমার Design-এ Box-এর ভূমিকা দিয়ে নাম দাও, Brand দিয়ে নয়।

#সংক্ষিপ্ত ইতিহাস: একটি অফিস কম্পিউটার থেকে C10K সমস্যা

  • ১৯৯০, CERN httpd। CERN-এ Tim Berners-Lee প্রথম Browser-এর পাশাপাশি প্রথম Web Server লেখেন।
  • ১৯৯৩, NCSA httpd। NCSA-তে লেখা (যে ল্যাব Mosaic Browser-ও বানিয়েছিল)। এটি CGI চালু করে, Web Server-এর একটি Program চালিয়ে তার Output ফেরত দেওয়ার প্রথম প্রচলিত পদ্ধতি।
  • ১৯৯৫, Apache। NCSA httpd-এর উন্নয়ন থেমে গেলে একদল Webmaster নিজেদের সংশোধনগুলো (“Patch”) জড়ো করে একসঙ্গে Apache HTTP Server নামে প্রকাশ করেন। বছরখানেকের মধ্যে এটি বিশ্বের সবচেয়ে বেশি ব্যবহৃত Web Server হয়ে যায়, এবং মোটামুটি দুই দশক সেই জায়গায় থাকে। এর শক্তি ছিল Module ব্যবস্থা: যে কেউ নতুন Feature যোগ করতে পারত।
  • ১৯৯৯, C10K সমস্যা। Dan Kegel প্রশ্ন তোলেন, একটি Server কীভাবে একসঙ্গে দশ হাজার Client সামলাবে। যে Server প্রতিটি Connection-এর জন্য একটি করে Process বা Thread রাখত, তার Memory অনেক আগেই ফুরিয়ে যেত। উত্তর ছিল Event-driven, Non-blocking I/O: অল্প কয়েকটি Thread, প্রত্যেকে অপারেটিং সিস্টেমের Readiness Notification দিয়ে হাজার হাজার Socket নজরে রাখে (Linux-এ epoll, FreeBSD ও macOS-এ kqueue)।
  • ২০০৪, Nginx। Igor Sysoev রাশিয়ার ব্যস্ত Portal Rambler সামলাতে Nginx (উচ্চারণ “engine-x”) তৈরি করেন, শুরু থেকেই সেই Event-driven মডেলে। Static File আর Reverse Proxy-র দ্রুত সামনের Server হিসেবে এটি দ্রুত ছড়িয়ে পড়ে। ২০১১ সালে এটিকে ঘিরে একটি কোম্পানি গড়ে ওঠে, ২০১৯ সালে F5 সেটি কিনে নেয়; Nginx এখনো Open Source, আর ২০২৪ সালে একজন মূল Developer FreeNginx নামে একটি Community Fork শুরু করেন।
  • ২০১৫, Caddy। Matt Holt Go-তে Caddy লেখেন; Let’s Encrypt বিনামূল্যে Certificate দেওয়া শুরু করার ঠিক পরেই এটি প্রথম জনপ্রিয় Server হয়ে ওঠে যা প্রতিটি Site-এর জন্য স্বয়ংক্রিয়ভাবে HTTPS Certificate নিয়ে আসে।

আজকের চিত্র মোটামুটি এমন, আর প্রতি বছর বদলায়: Nginx ও Apache দুটোই বিপুল সংখ্যক Website চালায়, Nginx এগিয়ে; Cloudflare একটি বড় অংশে দেখা যায়, কারণ সে লাখ লাখ Site-এর সামনে উত্তর দেয়; WordPress Hosting-এর কল্যাণে LiteSpeed শক্তিশালী চতুর্থ; বাকিটা Caddy, IIS, Node.js ও অন্যান্য। জরিপের সংখ্যা মেলে না, কারণ কেউ প্রতিটি Site গোনে (লাখ লাখ Parked Domain-সহ), কেউ শুধু সবচেয়ে ব্যস্তগুলো।

#Apache HTTP Server গভীরে

Apache (Program-টির নাম httpd, Debian ও Ubuntu-তে apache2) নমনীয়, অসাধারণভাবে Documented, এবং এখনো বহু Shared Hosting-এর Default।

#Configuration: Virtual Host, Directory ও Module

মূল File হলো httpd.conf (Red Hat পরিবার) বা apache2.conf (Debian পরিবার)। Debian বাকিটা Folder-এ ভাগ করে: প্রতিটি Site-এর জন্য sites-available/-এ একটি File, a2ensite দিয়ে চালু করা হয় (যা sites-enabled/-এ একটি Link বানায়), আর Module চালু হয় a2enmod দিয়ে।

মূল উপাদানগুলো:

  • VirtualHost Block: প্রতিটি Site-এর জন্য একটি Block। Apache IP, Port ও Host Header (ServerName, ServerAlias) দেখে Block বেছে নেয়, তাই একটি Server শত শত Domain Host করতে পারে।
  • DocumentRoot: যে Folder-এর File ওই Site-এর জন্য পাঠানো হয়।
  • Directory Block (এবং Location, Files): File System বা URL-এর একটি অংশের নিয়ম: কে Access পাবে, .htaccess পড়া হবে কি না, Directory Listing দেখানো হবে কি না।
  • Module প্রায় প্রতিটি Feature যোগ করে: mod_ssl (TLS), mod_rewrite (Regular Expression দিয়ে URL বদলানো ও Redirect), mod_proxy সঙ্গে mod_proxy_http বা mod_proxy_fcgi (App Server-এ Reverse Proxy), mod_headers (Header যোগ বা বাদ), mod_deflate (gzip), mod_php (প্রতিটি Apache Process-এর ভেতরেই PHP চালায়)।

একটি আসল Site: HTTP থেকে HTTPS-এ Redirect, File আসে public/ থেকে, আর /api/ যায় Port 8000-এর একটি App-এ।

# /etc/apache2/sites-available/example.conf
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/example/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    <Directory /var/www/example/public>
        Options -Indexes +FollowSymLinks   # Directory Listing বন্ধ
        AllowOverride None                 # .htaccess উপেক্ষা (দ্রুততর)
        Require all granted
    </Directory>

    ProxyPass        /api/ http://127.0.0.1:8000/
    ProxyPassReverse /api/ http://127.0.0.1:8000/
    RequestHeader set X-Forwarded-Proto "https"

    Header always set X-Content-Type-Options "nosniff"
    ErrorLog  ${APACHE_LOG_DIR}/example-error.log
    CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>
sudo a2enmod ssl proxy proxy_http headers
sudo a2ensite example
sudo apachectl configtest          # চালু Server-এ হাত দেওয়ার আগে "Syntax OK"
sudo systemctl reload apache2      # Graceful: চলমান Request আগে শেষ হয়

#.htaccess: Directory অনুযায়ী Override

.htaccess হলো একটি Content Folder-এর ভেতরে রাখা ছোট Config File। Directory Block অনুমতি দিলে (AllowOverride All বা অনুমোদিত Directive-এর তালিকা), Apache এর ভেতরের Directive-গুলো ওই Folder ও তার নিচের সবকিছুতে প্রয়োগ করে: Rewrite Rule, Redirect, Password সুরক্ষা, নিজস্ব Error Page, Cache Header।

কেন এটা আছে: Shared Hosting-এ শত শত গ্রাহক একটি Apache ভাগ করে, কেউই মূল Config বদলাতে পারে না। .htaccess দিয়ে প্রত্যেক গ্রাহক একটি File Upload করেই নিজের Site নিয়ন্ত্রণ করে, আর পরিবর্তন Reload ছাড়াই পরের Request থেকে কার্যকর হয়। যেমন WordPress তার “Pretty Permalink”-এর Rewrite Rule সেখানেই লেখে।

কেন এতে Performance কমে: Apache এই File-গুলো Cache করে না। প্রতিটি Request-এ তাকে লক্ষ্য Folder এবং Root পর্যন্ত প্রতিটি Parent Folder-এ .htaccess খুঁজতে হয়, যা পায় তা পড়ে পার্স করতে হয়। /blog/2026/10/post.html-এর একটি Request মানে প্রতিবার কয়েকটি বাড়তি File System খোঁজাখুঁজি।

#MPM: Apache কীভাবে অনেক Connection সামলায়

Apache-এর Connection সামলানোর পদ্ধতিও একটি বদলযোগ্য Module, যার নাম MPM (Multi-Processing Module)। ঠিক একটি লোড করতে হয়:

MPMমডেলশক্তিদুর্বলতা
preforkকয়েকটি Process, প্রতিটিতে একটি Thread; প্রতি Connection-এ একটি Processসম্পূর্ণ বিচ্ছিন্নতা; mod_php ও অন্যান্য Thread-safe নয় এমন Library-র জন্য বাধ্যতামূলকপ্রতিটি Process কয়েক Megabyte Memory খায়। ধীর বা অলস Keep-alive Client একটি পুরো Process আটকে রাখে
workerকয়েকটি Process, প্রতিটিতে অনেক Thread; প্রতি Connection-এ একটি ThreadConnection প্রতি অনেক কম Memoryঅলস Keep-alive Connection-ও Timeout না হওয়া পর্যন্ত একটি Thread আটকে রাখে
eventworker-এর মতো, সঙ্গে প্রতি Process-এ একটি Listener Thread যে অলস Keep-alive Connection নজরে রাখেRequest-এর ফাঁকে Thread মুক্ত হয়ে যায়, তাই অনেক অলস Client-এর খরচ কম। আধুনিক Linux-এ সাধারণ DefaultThread-safe Module লাগে: PHP mod_php-এর বদলে PHP-FPM দিয়ে চলে

মূল সীমা হলো MaxRequestWorkers (Apache 2.4-এর আগে নাম ছিল MaxClients): একসঙ্গে সর্বোচ্চ কতগুলো Request সামলানো যাবে। prefork-এ এটা Process-এর সংখ্যা; worker ও event-এ Process গুণ ThreadsPerChild। সবাই ব্যস্ত থাকলে নতুন Connection Kernel-এর Listen Queue-তে অপেক্ষা করে এবং একসময় Timeout হয়ে যায়। তখন Apache Log-এ সতর্ক করে যে Server MaxRequestWorkers-এ পৌঁছেছে।

Animation দেখায় কেন এটা গুরুত্বপূর্ণ: দশটি দ্রুত Client যে কারও জন্য সহজ; Keep-alive Connection-এ বসে থাকা এক হাজার ধীর Client মোটেও সহজ নয়।

অনেক Connection: Apache MPM বনাম Nginx

একই Server, Connection সামলানোর চারটি পদ্ধতি। নিচের নতুন ভিজিটরের কী হয়, লক্ষ করো।

পরিস্থিতি

Connection ছোট থাকলে প্রতি Connection-এ একটি Process ভালোই কাজ করে।

দ্রুত Client
ধীর Client
নতুন ভিজিটর
Web Server
Process / Thread৮টি অলস Process
Memory
ধাপ 1 / 4
1

prefork MPM-সহ Apache কিছু তৈরি Process রাখে; প্রতিটি একসঙ্গে ঠিক একটি Connection সামলাতে পারে।

#Nginx গভীরে

Nginx একটি লক্ষ্য নিয়ে তৈরি: অনেক Connection, কম Memory, চাপের মধ্যে কোনো চমক নয়। Internet-এ এটিই সবচেয়ে প্রচলিত Reverse Proxy ও Static File Server।

#Master ও Worker

Nginx চালু করলে একটি Master Process আর কয়েকটি Worker Process পাও:

  • Master root হিসেবে চলে। সে Configuration পড়ে ও যাচাই করে, Log File খোলে, এবং Port 80 ও 443-এ Listening Socket Bind করে (Linux-এ ১০২৪-এর নিচের Port-এর জন্য root লাগে)। তারপর Worker-দের চালু করে তত্ত্বাবধান করে। নিজে কখনো কোনো Client সামলায় না।
  • Worker-রা খোলা Listening Socket উত্তরাধিকারসূত্রে পায়, তারপর একটি সাধারণ User-এ বদলে যায় (user www-data; বা user nginx;)। সাধারণত প্রতি Core-এ একটি Worker থাকে (worker_processes auto;)। প্রতিটি Client Connection শুরু থেকে শেষ পর্যন্ত একটি Worker-ই সামলায়।

কোনো Worker Crash করলে Master নতুন একটি চালু করে। কোনো আক্রমণকারী Request পার্সিংয়ে Bug খুঁজে পেলেও সে root অধিকারহীন একটি Process-এ গিয়ে পড়বে। এই ভাগাভাগির কারণেই Reload হয় Graceful, নিচে দেখবে।

Nginx-এর Master ও Worker Process

কেন Nginx root হিসেবে Port 443 Bind করতে পারে, সাধারণ User হিসেবে সেবা দেয়, আর একটি Connection-ও না ফেলে Config বদলাতে পারে।

পরিস্থিতি

Master বিশেষ অধিকারের কাজগুলো একবার করে; Worker-রা root অধিকার ছাড়াই সেবা দেয়।

Admin
Masterroot
Worker (প্রজন্ম ১)www-data
Worker (প্রজন্ম ২)www-data
Client
systemctl start nginx
ধাপ 1 / 5
1

Admin Service চালু করে। Master Process root হিসেবে চালু হয়।

#Event Loop: কেন Memory প্রায় স্থির থাকে

প্রতিটি Worker একটি Thread, যা একটি Event Loop চালায়। সে কখনো একটি Client-এর জন্য বসে থাকে না। বরং Kernel-কে জিজ্ঞেস করে (Linux-এ epoll, FreeBSD ও macOS-এ kqueue) “আমার হাজার হাজার Socket-এর মধ্যে কোনগুলো তৈরি, বলো”, তারপর প্রতিটি তৈরি Socket-এ ছোট একটু কাজ করে (কিছু Byte পড়া, একটি Header পার্স করা, Response-এর একটি অংশ লেখা) এবং আবার অপেক্ষায় ফেরে। সব Socket Operation Non-blocking: কোনো Socket-এ এখনো Data না থাকলে Call সঙ্গে সঙ্গে ফিরে আসে, Worker পরের কাজে চলে যায়।

অলস একটি Connection (চিন্তারত একটি Keep-alive Client, ফোঁটা ফোঁটা Byte পাঠানো ধীর মোবাইল Client) Nginx-এর কাছে শুধু একটি ছোট Structure আর কিছু Buffer Memory, কিলোবাইটের হিসাবে, কোনো পুরো Process বা Thread নয়। এজন্যই দশ হাজার অলস Connection-এও Nginx-এর Memory প্রায় বদলায় না, আর যে ধীর Client prefork Apache-কে নিঃশেষ করে দিত, Nginx তা গায়েই মাখে না। প্রতি Worker-এর সীমা worker_connections (প্রায়ই ১০২৪ বা তার বেশি রাখা হয়); তাত্ত্বিক মোট সংখ্যা Worker × worker_connections, আর Proxy করা একটি Request দুটি Connection নেয় (Client-এর দিকে একটি, Upstream-এর দিকে একটি)।

#Config-এর গঠন

Nginx Configuration (/etc/nginx/nginx.conf, প্রায়ই conf.d/ বা sites-enabled/-এ include করা File-সহ) হলো Context (Block)-এর একটি Tree, যার ভেতরে ; দিয়ে শেষ হওয়া Directive:

Contextকী রাখে
main (সবার ওপরে)user, worker_processes, error_log, pid
eventsworker_connections ও Connection সামলানোর Option
httpসব HTTP Site-এর Setting: MIME Type, Log Format, gzip, Timeout, upstream Block, Cache Zone
serverএকটি Virtual Host: listen, server_name, Certificate
locationএকটি Server-এর ভেতরে একটি URL Path-এর নিয়ম: root, try_files, proxy_pass
upstreamLoad Balancing-এর জন্য Backend Server-দের নামযুক্ত একটি দল

বাইরের Block-এ সেট করা Directive ভেতরের Block উত্তরাধিকারসূত্রে পায়, যদি না সেখানে বদলানো হয়। এখানে একটি Site-এর সম্পূর্ণ, বাস্তবসম্মত Configuration:

user  www-data;
worker_processes  auto;
error_log  /var/log/nginx/error.log warn;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout  65s;
    server_tokens off;                 # Version নম্বর লুকাও
    client_max_body_size 20m;          # Default 1m

    log_format main '$remote_addr "$request" $status $body_bytes_sent '
                    'rt=$request_time urt=$upstream_response_time';
    access_log /var/log/nginx/access.log main;

    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;

    upstream app {
        least_conn;
        server 10.0.1.11:8000 max_fails=3 fail_timeout=10s;
        server 10.0.1.12:8000 max_fails=3 fail_timeout=10s;
        server 10.0.1.13:8000 backup;
        keepalive 32;                  # App-এর Connection আবার ব্যবহার
    }

    proxy_cache_path /var/cache/nginx keys_zone=pages:10m max_size=1g inactive=10m;
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;

    server {                           # HTTP: শুধু Redirect
        listen 80;
        server_name example.com www.example.com;
        return 301 https://example.com$request_uri;
    }

    server {
        listen 443 ssl;
        http2 on;                      # পুরোনো Version: listen 443 ssl http2;
        server_name example.com;
        ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        root /var/www/example/public;

        location /assets/ {            # Fingerprint করা File: এক বছর Cache
            expires 1y;
            add_header Cache-Control "public, immutable";
        }

        location = /login {
            limit_req zone=login burst=10 nodelay;
            proxy_pass http://app;
        }

        location /api/ {
            proxy_pass http://app;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_connect_timeout 5s;
            proxy_read_timeout    30s;
            proxy_cache pages;
            proxy_cache_valid 200 1m;
            proxy_cache_use_stale error timeout updating;
        }

        location / {
            try_files $uri $uri/ /index.html;   # Single-page App-এর Fallback
        }
    }
}

#Nginx কীভাবে একটি location বেছে নেয়

একটি server-এর ভেতরে ঠিক একটি location একটি Request সামলায়। ক্রমটি নির্দিষ্ট, আর Interview-তে প্রিয় প্রশ্ন:

  1. Exact Match location = /login: Path হুবহু এটা হলে এখানেই থামো।
  2. না হলে Nginx সাধারণ Prefix Location-গুলোর (location /api/, location /) মধ্যে সবচেয়ে লম্বা মিলে যাওয়া Prefix খুঁজে মনে রাখে। সেই লম্বা Prefix-এ ^~ চিহ্ন থাকলে (location ^~ /assets/) সেটাই সঙ্গে সঙ্গে ব্যবহার হয়, Regular Expression আর দেখা হয় না।
  3. না হলে Nginx Regular Expression Location-গুলো (~ Case-sensitive, ~* Case-insensitive) File-এ যে ক্রমে আছে সেই ক্রমে চেষ্টা করে। প্রথম যেটা মেলে, সেটাই জেতে।
  4. কোনো Regular Expression না মিললে মনে রাখা সবচেয়ে লম্বা Prefix ব্যবহার হয়।

তাই /images/a.png-এর জন্য location ~* \.(png|jpg)$ জিতে যায় location /images/-এর ওপর, যদি না তুমি location ^~ /images/ লেখো। File-এর ক্রম গুরুত্বপূর্ণ শুধু Regular Expression-গুলোর মধ্যে।

#Static File পাঠানো

  • root URL-কে Path-এ রূপান্তর করে: root /var/www/site; থাকলে URL /css/app.css হয়ে যায় /var/www/site/css/app.css। (alias Path-এর মিলে যাওয়া অংশটি জুড়ে না দিয়ে বদলে দেয়; দুটো গুলিয়ে ফেলা 404-এর একটি পুরোনো কারণ।)
  • try_files $uri $uri/ /index.html; একে একে প্রতিটি বিকল্প চেষ্টা করে: File, তারপর Index File-সহ Folder, তারপর একটি Fallback। Fallback হতে পারে একটি File (Single-page App-এর জন্য) বা একটি নামযুক্ত Location যা App-এ Proxy করে (PHP ও Rails-এর প্রচলিত ধরন: “File থাকলে পাঠাও, না থাকলে App-কে জিজ্ঞেস করো”)।
  • sendfile on; Kernel-কে File-এর Data সরাসরি Page Cache থেকে Socket-এ কপি করতে দেয়, Nginx-এর Memory দিয়ে না ঘুরিয়ে (“Zero-copy”)। tcp_nopush-এর সঙ্গে মিলে Header ও File-এর শুরু পূর্ণ Packet-এ যায়।
  • gzip on; (বা আলাদা brotli Module) Text Type তাৎক্ষণিক Compress করে; gzip_static on; আগে থেকে Compress করা .gz File থাকলে সেটাই পাঠায়, তাতে CPU বাঁচে।
  • Cache Header: expires ও add_header Cache-Control Browser ও CDN-কে জানায় একটি File কতক্ষণ রাখা যাবে। Fingerprint করা File (app.3f9a1c.js) এক বছর Cache করা যায়; HTML সাধারণত নয়।

#Reverse Proxy

proxy_pass http://app; Request-টি একটি Upstream Group বা একটি নির্দিষ্ট Address-এ পাঠায় (http://127.0.0.1:3000, বা একটি Unix Socket http://unix:/run/app.sock)। চারটি জিনিস প্রায় সবসময় Configure করতে হয়:

  • Header। App দেখে Connection আসছে Nginx থেকে, তাই Nginx যা জানে তা জানিয়ে দেয়: Host (আসল Host Name), X-Real-IP ও X-Forwarded-For (Client IP-এর শৃঙ্খল), X-Forwarded-Proto (Client TLS ব্যবহার করলে https)। App-কে এগুলো বিশ্বাস করতে হবে শুধু Proxy থেকে এলে।
  • Timeout। proxy_connect_timeout (Connection স্থাপন), proxy_send_timeout ও proxy_read_timeout (App থেকে পরপর দুবার পড়ার মাঝের সময়, Default ৬০ সেকেন্ড)। Read Timeout শেষ হলে Client পায় 504।
  • Buffering। Default অনুযায়ী Nginx App-এর Response যত দ্রুত সম্ভব Buffer-এ পড়ে নেয় (বড় হলে একটি অস্থায়ী File-এ), তারপর Client-এর গতিতে ধীরে ধীরে তাকে পাঠায়। দামি App Worker ধীর একটি ফোনের অপেক্ষায় না থেকে সঙ্গে সঙ্গে মুক্ত হয়ে যায়। Server-sent Event-এর মতো Streaming Response-এর জন্য এটা বন্ধ করো (proxy_buffering off;)। Request Body-ও Buffer হয়, তাই App একটি Upload পায় সেটি পুরোপুরি পৌঁছানোর পরেই।
  • Upgrade। WebSocket-এর জন্য proxy_http_version 1.1 আর Upgrade ও Connection Header পার করে দিতে হয়।

#Load Balancing

একটি upstream Block Server-দের তালিকা আর একটি পদ্ধতি রাখে:

পদ্ধতিDirectiveআচরণ
Round Robin(Default)প্রতিটি Request পালাক্রমে পরের Server-এ যায়
Weightedserver a weight=3;weight ৩-এর Server তিনগুণ ভাগ পায়; বড় Machine বা Canary-র জন্য
Least Connectionsleast_conn;পরের Request যায় সবচেয়ে কম সক্রিয় Connection-ওয়ালা Server-এ; Request-এর সময় ওঠানামা করলে ভালো
IP Haship_hash;Client IP ঠিক করে কোন Server, তাই একজন Client বারবার একই Server-এ যায় (“Sticky”); Session App-এর Memory-তে থাকলে কাজে লাগে
Generic Hashhash $request_uri consistent;যেকোনো Key; consistent থাকলে নতুন Server যোগ করলে অল্প কিছু Key-ই সরে (Cache-এর জন্য ভালো)

Open-source Nginx Passive Health Check ব্যবহার করে: একটি Server-এ Connect করা বা তার থেকে পড়া fail_timeout-এর (Default ১০ সেকেন্ড) মধ্যে max_fails বার (Default ১) ব্যর্থ হলে সেটি fail_timeout সময়ের জন্য অনুপলব্ধ চিহ্নিত হয়, তারপর আবার চেষ্টা করা হয়। ব্যর্থ Request-টি পরের Server-এ আবার চেষ্টা করা হয় (proxy_next_upstream, Default অনুযায়ী শুধু Error ও Timeout-এ, নিরাপদ ক্ষেত্রে)। Active Health Check (নিয়মিত /health যাচাই) আছে বাণিজ্যিক Nginx Plus, HAProxy, Envoy ও Cloud Load Balancer-এ।

upstream Block-এ Load Balancing

Nginx-এর পেছনে তিনটি একই রকম App Server। প্রতিটি পরিস্থিতি একটি Balancing পদ্ধতি বা একটি ব্যর্থতা।

পরিস্থিতি

Default: প্রতিটি Request পালাক্রমে পরের Server-এ যায়।

Client A203.0.113.7
Client B198.51.100.4
Nginxupstream app
app110.0.1.11
app210.0.1.12
app310.0.1.13
#1
ধাপ 1 / 4
1

Request ১ যায় app1-এ।

#Caching ও Rate Limiting

  • proxy_cache Upstream-এর Response Disk-এ জমা রাখে, তার Index থাকে Shared Memory-তে (keys_zone)। একই Key-র (Default: Scheme, Host ও URI) পরের Request App-কে না ছুঁয়েই Cache থেকে উত্তর পায়। proxy_cache_use_stale App বন্ধ বা ধীর থাকলে পুরোনো কপি দেয়, আর proxy_cache_lock মেয়াদোত্তীর্ণ একটি Entry নতুন করে আনতে শুধু একটি Request-কে যেতে দেয়, বাকিরা অপেক্ষা করে; এতে App হুড়োহুড়ি (Stampede) থেকে বাঁচে। যে Response Cookie সেট করে বা Cache-Control: private বলে, সেগুলো Default অনুযায়ী Cache হয় না।
  • limit_req প্রতিটি Key-র (সাধারণত Client IP) জন্য একটি “Leaky Bucket” ব্যবহার করে: rate=5r/s সঙ্গে burst=10 অল্প সময়ের ঝাঁক সহ্য করে, তারপর বাড়তিগুলো প্রত্যাখ্যান করে। Default প্রত্যাখ্যানের Status 503; অনেক Team limit_req_status 429; সেট করে, যাতে Client সঠিক “Too Many Requests” দেখে। limit_conn প্রতিটি Key-র একসঙ্গে Connection সংখ্যা সীমিত করে।

#TLS, HTTP/2 ও HTTP/3

ssl_certificate ও ssl_certificate_key Certificate Chain ও Private Key দেখায় (Key File শুধু root পড়তে পারবে, যা Master অধিকার ছাড়ার আগেই খুলে নেয়)। ssl_protocols TLSv1.2 TLSv1.3; পুরোনো Version বন্ধ করে। HTTP/2 চালু হয় http2 on; দিয়ে (পুরোনো Version: listen 443 ssl http2;)। QUIC-এর ওপর HTTP/3 সমর্থিত Version 1.25 থেকে, listen 443 quic; আর সেটা ঘোষণা করা একটি Alt-Svc Header দিয়ে, কারণ Browser প্রথমে TCP দিয়ে Connect করে তারপর Upgrade করে। Nginx নিজে Certificate আনে না; certbot-এর মতো একটি Tool সেগুলো এনে Nginx Reload করে।

#নিরাপদে Config বদলানো: Test ও Graceful Reload

sudo nginx -t            # প্রতিটি File পার্স ও যাচাই; ভুল থাকলে তার লাইন দেখায়
sudo nginx -s reload     # অথবা: systemctl reload nginx (Master-কে একটি HUP Signal পাঠায়)

Reload-এ Master আবার Configuration পড়ে। বৈধ হলে সে দরকারি নতুন Socket খোলে, নতুন Config নিয়ে নতুন Worker চালু করে আর পুরোনো Worker-দের নতুন Connection নেওয়া বন্ধ করতে বলে; তারা চলমান Request শেষ করে বিদায় নেয়। একটি Connection-ও কাটে না। নতুন Config অবৈধ হলে Master Error Log-এ লেখে এবং পুরোনো Configuration-ই চালিয়ে যায়। তবুও আগে nginx -t চালানো অভ্যাস করো, কারণ এটা সঙ্গে সঙ্গে ভুল দেখায়, আর ভাঙা File নিয়ে restart (বন্ধ করে চালু) করলে তোমার কোনো Web Server-ই থাকবে না। WebSocket-এর মতো দীর্ঘস্থায়ী Connection পুরোনো Worker-কে বাঁচিয়ে রাখে; worker_shutdown_timeout তার সীমা ঠিক করে।

#Nginx PHP চালাতে পারে না: PHP-FPM-এ FastCGI

Nginx-এর ভেতরে কোনো Language Runtime নেই। PHP-র জন্য সে আলাদা একটি PHP Process Pool, PHP-FPM-এর সঙ্গে FastCGI-তে কথা বলে, সাধারণত একটি Unix Socket দিয়ে:

location / {
    try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

প্রতিটি Language-এ একই ধারণা: Nginx File পাঠায় আর বাকি সব একটি App Server-এ Proxy করে (HTTP, FastCGI বা uwsgi Protocol দিয়ে)।

#OpenResty ও Kubernetes-এ Nginx

OpenResty হলো একটি দ্রুত Lua Engine-সহ Nginx, তাই তুমি Event Loop-এর ভেতরেই Lua-তে Request-এর যুক্তি লিখতে পারো: নিজস্ব Authentication, Dynamic Routing, Database-ভিত্তিক Rate Limit। Kong ও অন্যান্য API Gateway এর ওপর তৈরি। Kubernetes-এ Nginx দীর্ঘদিন সবচেয়ে প্রচলিত Ingress Controller ছিল, অর্থাৎ যে Reverse Proxy বাইরের Traffic-কে Host Name ও Path দেখে Service-এ পাঠায়। Community-র ingress-nginx Project-টি ২০২৬ সালে Kubernetes Project অবসরে পাঠিয়েছে; নতুন Cluster সাধারণত Gateway API ব্যবহার করে, Envoy, Nginx (F5-এর নিজস্ব Controller), Traefik বা HAProxy-ভিত্তিক Controller দিয়ে।

নিজেকে যাচাই করোপ্রশ্ন

Nginx-এ location /images/ আছে, আর File-এ তার পরে location ~* \.(png|jpg)$। /images/cat.png কে সামলাবে?

#Apache বনাম Nginx

ApacheNginx
Architectureপ্রতি Connection-এ Process বা Thread (prefork / worker); event MPM অলস Connection-এর জন্য একটি Listener যোগ করেনির্দিষ্ট সংখ্যক Single-threaded, Event-driven Worker
অনেক Connection-এ Memoryএকসঙ্গে Connection বাড়লে বাড়ে (prefork-এ সবচেয়ে বেশি)প্রায় স্থির: অলস Connection প্রতি কয়েক কিলোবাইট
Static Fileভালোঅসাধারণ; এটা ব্যবহারের পুরোনো কারণ
Dynamic ContentProcess-এর ভেতরেই PHP চালাতে পারে (mod_php), বা App Server-এ Proxy করেসবসময় Proxy করে: PHP-FPM-এ FastCGI, App Server-এ HTTP
Directory অনুযায়ী Config.htaccess, Reload লাগে নানেই: সব Config কেন্দ্রীয়, Reload দিয়ে বদলায়
Config-এর ধরনHTML Tag-এর মতো করে লেখা Directive Block; খুব শক্তিশালী mod_rewrite;-সহ নেস্টেড Block; Rewrite-এর বদলে location ও try_files
Moduleবিশাল Ecosystem, চলার সময় LoadModule দিয়ে লোডঅনেকগুলো ভেতরেই; Third-party Module Compile করে বা ঠিক ওই Version-এর জন্য বানানো Dynamic Module হিসেবে লোড
আজকের সাধারণ ভূমিকাShared Hosting, পুরোনো App, .htaccess লাগে এমন SetupReverse Proxy, Load Balancer, Static File, প্রায় যেকোনো Stack-এর সামনে

একটি প্রচলিত সমন্বয় হলো Apache-এর সামনে Nginx: Nginx TLS Terminate করে, Static File পাঠায়, ধীর Client সামলায় ও Buffer করে, আর শুধু Dynamic Request 127.0.0.1:8080-এ Listen করা Apache-এ Proxy করে, যেখানে পুরোনো একটি PHP Application-এর জন্য .htaccess ও mod_php চলতে থাকে। তখন Apache-এর mod_remoteip X-Forwarded-For থেকে আসল Client IP ফিরিয়ে আনে।

#অন্যান্য Web Server

#Caddy: Default-ভাবেই HTTPS

Caddy হলো একটি একক Go Binary, সঙ্গে ছোট একটি Config File, Caddyfile। এর সবচেয়ে পরিচিত Feature হলো স্বয়ংক্রিয় HTTPS: Config-এর প্রতিটি Site Name-এর জন্য Caddy ACME Protocol ব্যবহার করে Let’s Encrypt থেকে (বিকল্প হিসেবে আরেকটি বিনামূল্যের Certificate Authority থেকে) Certificate আনে, HTTP থেকে HTTPS-এ Redirect করে, এবং মেয়াদ শেষের আগেই Certificate নবায়ন করে। HTTP/2 ও HTTP/3 Default-ভাবে চালু। TLS-সহ একটি সম্পূর্ণ Reverse Proxy:

example.com {
    encode gzip zstd
    root * /var/www/example/public
    reverse_proxy /api/* localhost:8000
    file_server
}

Certificate কীভাবে আসে: Certificate Authority-কে যাচাই করতে হয় যে নামটি তোমার নিয়ন্ত্রণে। HTTP-01 Challenge-এ সে Caddy-কে একটি Token দেয়, তারপর http://example.com/.well-known/acme-challenge/<token> আনে; শুধু ওই নামের আসল Server-ই উত্তর দিতে পারে। তাই DNS আগে থেকেই Server-এর দিকে থাকতে হবে আর Port 80 Internet থেকে পৌঁছানো যেতে হবে। বিকল্প হলো TLS-ALPN-01 (Port 443-এ প্রমাণ) আর DNS-01 (তোমার DNS Provider-এর API দিয়ে একটি TXT Record তৈরি করে প্রমাণ; Wildcard Certificate বা বাইরে থেকে পৌঁছানো যায় না এমন Machine-এর Certificate পাওয়ার একমাত্র উপায়)। Let’s Encrypt-এর Certificate এখন ৯০ দিন টেকে, আরও ছোট মেয়াদ চালু হচ্ছে; মেয়াদের প্রায় এক-তৃতীয়াংশ বাকি থাকলে Caddy নবায়ন করে, ৯০ দিনের Certificate-এর জন্য মেয়াদ শেষের মোটামুটি ৩০ দিন আগে।

Caddy নিজেই Certificate আনে (ACME)

Certificate Authority-কে প্রমাণ দেখাতে হবে যে এই Server সত্যিই example.com-এর হয়ে উত্তর দেয়।

পরিস্থিতি

Port 80-এ দেওয়া একটি Token নামটির নিয়ন্ত্রণ প্রমাণ করে।

Caddy203.0.113.10
Let’s EncryptACME Certificate Authority
DNSexample.com
Browser
Caddyfile: example.com → Certificate দরকার
ধাপ 1 / 10
1

Caddy চালু হয়ে এমন একটি Site Name দেখে, যার বৈধ Certificate তার কাছে নেই। সে একটি Account Key তৈরি করে নিজে থেকেই কাজ শুরু করে।

#বাকিরা

  • Microsoft IIS (Internet Information Services) হলো Windows Server-এ থাকা Web Server। একটি Kernel Driver (http.sys) Request গ্রহণ করে Application Pool-এর হাতে দেয়; প্রতিটি Pool কিছু Worker Process (w3wp.exe) নিয়ে গঠিত, যেগুলো আলাদাভাবে Recycle করা যায়। এটি ASP.NET Application-এর প্রচলিত ঠিকানা, আর Configure করা হয় একটি গ্রাফিক্যাল Manager বা web.config File দিয়ে (এটাই তার .htaccess)।
  • LiteSpeed Web Server (বাণিজ্যিক) Nginx-এর মতো Event-driven, কিন্তু সরাসরি Apache-এর Configuration ও .htaccess File পড়ে, তাই Hosting কোম্পানি গ্রাহকদের কিছু না বদলিয়েই Apache সরিয়ে দিতে পারে। এর বিনামূল্যের সহোদর OpenLiteSpeed-এর নিজস্ব Config Format আছে, তবে .htaccess-এর Rewrite Rule বোঝে। এর ভেতরের Page Cache LSCache-এর একটি জনপ্রিয় WordPress Plugin আছে, এজন্যই WordPress Hosting-এ এটা বেশি দেখা যায়।
  • Lighttpd (“lighty”, ২০০৩) C10K সমস্যার উত্তর হিসেবে লেখা হয়েছিল: Event-driven ও খুব ছোট। আজ এটা বেশি দেখা যায় Embedded Device ও Router-এ।
  • Apache Tomcat একটি Java Servlet Container (নিচে দেখো), যা নিজেও একটি দক্ষ HTTP Server; Jetty ও Undertow-ও তাই।
  • Language-এর নিজস্ব Server। Node-এর http Module আর Go-এর net/http আসল Production Server: Application নিজেই একটি Port-এ Listen করে। Python-এর python -m http.server আর PHP-র php -S শুধু Development-এর জন্য; তাদের Documentation-ই তা বলে (নিরাপত্তা নেই বললেই চলে, Concurrency সামান্য)।

#Application Server: তোমার Code আসলে যেখানে চলে

Nginx-এর মতো Web Server Python, Ruby, Java বা PHP-র কিছুই জানে না। কাউকে Language Runtime লোড করে রাখতে হয়, তোমার Application Code আর তার Database Connection Memory-তে গরম রাখতে হয়, এবং একটি Request তার ভেতর দিয়ে চালাতে হয়। সেটাই Application Server। দুজনের মাঝখানে থাকে একটি Interface: Request হস্তান্তর আর Response ফেরত পাওয়ার একটি সম্মত পদ্ধতি। এগুলো মোটামুটি এই ক্রমে এসেছে।

#CGI: প্রতিটি Request-এ নতুন Process

CGI (Common Gateway Interface, ১৯৯৩)-তে Web Server প্রতিটি Request-এর জন্য একটি নতুন Process চালু করে, Request-এর তথ্য Environment Variable-এ (REQUEST_METHOD, QUERY_STRING…) আর Body Standard Input-এ দেয়, এবং Program যা Print করে তা Response হিসেবে পাঠায়। সহজ ও যেকোনো Language-এ চলে, কিন্তু প্রতিটি Request-এ Interpreter চালু করা, Code লোড করা আর Database Connection খোলা ধীর, আর হঠাৎ Traffic বাড়লে হঠাৎ অনেক Process। CGI এখন টিকে আছে শুধু ছোট Embedded Tool-এ।

#FastCGI ও PHP-FPM: Process বাঁচিয়ে রাখো

FastCGI দীর্ঘক্ষণ চলা Process-এর একটি Pool রাখে। Web Server একটি Binary Protocol-এ Socket দিয়ে প্রতিটি Request একটি মুক্ত Process-কে পাঠায়, আর Process পরের Request-এর জন্য বেঁচে থাকে। PHP-FPM (FastCGI Process Manager) আজ PHP চালানোর প্রচলিত পদ্ধতি, Nginx, Apache (mod_proxy_fcgi-সহ event MPM) বা Caddy-র পেছনে। Production-এ এর Pool Setting গুরুত্বপূর্ণ:

  • pm = static ঠিক pm.max_children সংখ্যক Process রাখে; pm = dynamic pm.min_spare_servers থেকে pm.max_spare_servers পর্যন্ত অলস রাখে, সর্বোচ্চ pm.max_children; pm = ondemand Request এলে তবেই Process চালু করে আর অলস Timeout-এর পরে বন্ধ করে (নিরিবিলি Site-এ Memory বাঁচে)।
  • pm.max_children একসঙ্গে PHP Request-এর কঠোর সীমা। Memory দেখে এর মান ঠিক করো: খালি RAM ভাগ একটি PHP Process-এর Memory। সব Child ব্যস্ত থাকলে নতুন Request Socket-এ সারিতে দাঁড়ায়; Log বলে server reached pm.max_children setting, ব্যবহারকারী ধীর Page দেখে, তারপর Nginx দেয় 504 (সারি উপচে পড়লে 502)।
  • pm.max_requests এতগুলো Request-এর পর একটি Process বদলে নতুন করে, Memory Leak-এর বিরুদ্ধে নিরাপত্তা জাল; request_terminate_timeout বেশিক্ষণ চলা Request মেরে ফেলে।

#Python: WSGI ও ASGI

  • WSGI (Web Server Gateway Interface) Python-এর Standard: App হলো একটি Callable, যা Request-এর Environment নেয় আর Response ফেরত দেয়। একটি Request শেষ না হওয়া পর্যন্ত একটি Worker দখল করে রাখে। Framework: Django, Flask। Server: Gunicorn, একটি Pre-fork Server, যার Master N সংখ্যক Worker Process চালু করে (শুরুর সাধারণ হিসাব ২ × Core + ১) এবং যেগুলো Crash করে বা --timeout (Default ৩০ সেকেন্ড) পেরোয় সেগুলো আবার চালু করে; আর uWSGI, খুব Configurable কিন্তু এখন শুধু রক্ষণাবেক্ষণ মোডে।
  • ASGI (Asynchronous Server Gateway Interface) হলো Async উত্তরসূরি: একটি Worker I/O-র অপেক্ষায় থাকা হাজার হাজার Request পালাক্রমে সামলাতে পারে, এবং WebSocket ও দীর্ঘস্থায়ী Connection সমর্থন করে। Framework: FastAPI, Starlette, Django (Async View)। Server: Uvicorn, Hypercorn, Daphne। Production-এ Uvicorn প্রায়ই কয়েকটি Worker Process নিয়ে চলে (তার নিজের --workers Option, বা Uvicorn Worker Class-সহ Gunicorn-এর অধীনে)।

#Ruby: Rack, Puma ও Unicorn

Rack হলো Ruby-র Interface (Rails, Sinatra)। Rails-এর Default Puma প্রতিটি Process-এর ভেতরে Thread আর কয়েকটি Worker Process (Cluster Mode) একসঙ্গে ব্যবহার করে, তাই সব Core কাজে লাগায় আবার I/O-র অপেক্ষাও কাজে লাগায়। Unicorn একটি কড়া Pre-fork Server, প্রতি Process-এ একটি Request, যা Nginx-এর পেছনে বসার জন্য বানানো, যাতে Nginx তাকে ধীর Client থেকে আড়াল করে।

#Java: Servlet Container ও Application Server

Java Web App Servlet API বাস্তবায়ন করে। একটি Servlet Container (Tomcat, Jetty, Undertow) একটি দীর্ঘক্ষণ চলা JVM-এর ভেতরে চলে, নিজেই HTTP গ্রহণ করে এবং একটি Thread Pool-এ তোমার Servlet ডাকে। আগে App প্যাকেট করা হতো একটি WAR File হিসেবে এবং চলমান Tomcat-এ Deploy করা হতো; এখন Spring Boot সাধারণত Tomcat (বা Jetty, Undertow) একটি চালানোযোগ্য JAR-এর ভেতরে Embed করে, তাই “App-ই Server”। একটি পূর্ণাঙ্গ Application Server (Jakarta EE, আগে Java EE: WildFly / JBoss, WebLogic, WebSphere, GlassFish) Servlet Container-এর ওপর Transaction, Messaging, Connection Pool ও আরও Enterprise Service যোগ করে।

#.NET, Node.js ও Go

  • .NET: ASP.NET Core App চলে Kestrel-এ, Framework-এর ভেতরে থাকা দ্রুত Cross-platform Server। TLS, Static File ও Process ব্যবস্থাপনার জন্য একে প্রায়ই Windows-এ IIS-এর পেছনে, বা Linux-এ Nginx বা Cloud Load Balancer-এর পেছনে রাখা হয়।
  • Node.js (এবং Next.js, Express, NestJS): Application নিজেই Server। তোমার Code listen(3000) ডাকে এবং একটি Event Loop-এ, একটি Core-এ চলে। সব Core কাজে লাগাতে কয়েকটি Process চালাও: Node-এর cluster Module, Cluster Mode-এ PM2 (যা Crash হলে আবার চালু করে এবং Downtime ছাড়া Reload করে), অথবা Load Balancer-এর পেছনে কয়েকটি Container।
  • Go: net/http-সহ একটি Compile করা Binary; আর কিছু Install করতে হয় না। তবুও TLS, Routing ও কয়েকটি কপি চালানোর জন্য প্রায়ই একটি Load Balancer-এর পেছনে রাখা হয়।
LanguageInterfaceসাধারণ App Serverসাধারণত সামনে থাকে
PHPFastCGI (বা mod_php)PHP-FPMNginx, Apache, Caddy, LiteSpeed
Python (Sync)WSGIGunicorn, uWSGINginx
Python (Async)ASGIUvicorn, HypercornNginx, Cloud Load Balancer
RubyRackPuma, UnicornNginx
Java / KotlinServlet (Jakarta EE)Tomcat, Jetty, Undertow (প্রায়ই Spring Boot-এ Embed করা); WildFly, WebLogicNginx, Cloud Load Balancer, Apache
C#ASP.NET Core HostingKestrelIIS, Nginx, Cloud Load Balancer
JavaScript / TypeScriptনেই: App নিজেই HTTP-তে Listen করেNode.js + PM2 বা clusterNginx, Cloud Load Balancer, CDN
Goনেই: net/httpBinary নিজেইCloud Load Balancer, Nginx

Web Server কীভাবে তোমার Code-এর সঙ্গে কথা বলে

প্রতি Request-এ একটি Process (CGI) থেকে দীর্ঘক্ষণ চলা Worker-এর Pool পর্যন্ত, আর Pool ফুরিয়ে গেলে কী হয়।

পরিস্থিতি

প্রতিটি Request-এ সম্পূর্ণ নতুন একটি Process: সহজ, কিন্তু চাপের মধ্যে ধীর।

Browser
Web ServerNginx / Apache
Process ManagerFPM / Gunicorn Master
Worker
Database
GET /cgi-bin/search?q=shoes
ধাপ 1 / 6
1

একটি CGI Script-এর Request আসে।

#Request-এর পথে অন্যান্য Software

Web Server-এর সঙ্গে পথ ভাগ করে আরও বিশেষায়িত কিছু Tool:

  • HAProxy (High Availability Proxy, ২০০০ থেকে) TCP (Layer 4) ও HTTP (Layer 7)-এর জন্য একটি নিবেদিত Load Balancer। সে File পাঠায় না; সে Balance করে, আর তাতে অত্যন্ত দ্রুত ও নিখুঁত: Active Health Check, প্রতি Server-এর Connection Queue, Rate Limit ও Sticky-র জন্য Stick Table, বিস্তারিত Statistics। এর PROXY protocol সাধারণ TCP-তে Client IP পৌঁছে দেয়।
  • Envoy (Lyft-এ তৈরি, এখন Cloud Native Computing Foundation-এর Project) Microservice-এর জন্য তৈরি আধুনিক Proxy। এর Configuration চলন্ত অবস্থায় API দিয়ে বদলানো যায়, যাদের বলে xDS, কোনো Reload ছাড়াই; এজন্যই Istio-র মতো Service Mesh প্রতিটি Service-এর পাশে একটি Envoy Sidecar চালায়: প্রতিটি Call-এর Retry, Timeout, Mutual TLS ও Metrics সে সামলায়। অনেক API Gateway ও Kubernetes Gateway Controller এর ওপর তৈরি।
  • Traefik এমন একটি Reverse Proxy যা নিজে নিজেই Service খুঁজে নেয়: Docker Label বা Kubernetes Resource নজরে রাখে, Container চালু-বন্ধ হলে Route তৈরি করে, আর ACME দিয়ে স্বয়ংক্রিয়ভাবে Certificate আনে। ছোট Cluster ও Home Lab-এ জনপ্রিয়।
  • Varnish একটি বিশুদ্ধ HTTP Cache (“Web Accelerator”), যা Web Server-এর সামনে বসে, Caching-এর নিয়মের জন্য তার নিজস্ব Configuration Language আছে। সে নিজে TLS Terminate করে না, তাই সাধারণত Nginx, HAProxy বা একটি আলাদা TLS Proxy-র পেছনে বসে।
  • Managed সমতুল্য একই কাজ Service হিসেবে করে: AWS ALB (HTTP, Layer 7: Path ও Host দেখে Routing, WebSocket, gRPC) ও NLB (TCP/UDP, Layer 4, স্থির IP, খুব বেশি Throughput), CloudFront ও Cloudflare (CDN, Edge TLS, Caching, WAF), Google Cloud Load Balancing। তোমাকে Patch বা Scale করতে হয় না, কিন্তু একই ধারণাগুলো Configure করতে হয়: Listener, Target Group, Health Check, Timeout।

Kubernetes Ingress Controller একই ধারণা, একটি Cluster-এর জন্য মানিয়ে নেওয়া। Cluster-এর ভেতরে Pod আসে-যায়, তাই তাদের IP একটি Config File-এ লিখে রাখা যায় না। একটি Ingress Controller (বা নতুন, একটি Gateway API Controller) হলো একটি Reverse Proxy (Nginx, Envoy, Traefik, HAProxy, বা একটি Cloud Load Balancer), যা Cluster নজরে রাখে এবং Service বদলালেই নিজের Routing নতুন করে লেখে। Traffic যায়: Cloud Load Balancer → Ingress Controller Pod → Service → Application Pod।

#প্রচলিত Production Stack

StackRequest-এর পথমন্তব্য
LAMPApache (mod_php বা PHP-FPM) → PHP → MySQL, Linux-এক্লাসিক Shared Hosting Stack; WordPress, অনেক পুরোনো App
LEMPNginx → PHP-FPM → MySQL, Linux-এ (উচ্চারণ engine-x বলে “E”)আধুনিক PHP Stack; VPS-এ Laravel, WordPress
PythonNginx → Gunicorn (Django) বা Uvicorn (FastAPI) → PostgreSQLএকই Machine-এ Nginx ও App-এর মাঝে Unix Socket
RubyNginx → Puma → Rails → PostgreSQL, Redisআগে থেকে Compile করা Asset Nginx পাঠায়
JavaNginx বা ALB → Embedded Tomcat-সহ Spring BootLoad Balancer-এর পেছনে কয়েকটি একই রকম JVM
Node.jsNginx বা ALB → Node + PM2 (Cluster Mode)Next.js-এর next start একটি Node Server
KubernetesCloud Load Balancer → Ingress / Gateway → Service → Pod (প্রত্যেকটির নিজস্ব App Server)Rolling Deploy ধীরে ধীরে Pod বদলায়
ServerlessCloudFront → Lambda (আর Static File-এর জন্য S3)সামলানোর মতো কোনো Web Server-ই নেই

শেষ সারিটি এই Site নিজেই যেভাবে চলে: OpenNext Next.js App-কে CloudFront-এর পেছনে AWS Lambda-র জন্য প্যাকেট করে। কোথাও কোনো Nginx নেই; CloudFront TLS Terminate করে ও Cache করে, S3 Static File পাঠায়, আর Lambda প্রতিটি Request-এ App Code চালায়।

একটি Production Web Stack

প্রতিটি Box-এর একটি কাজ। একটি Dynamic Page, একটি Static File, একটি Node-এর ব্যর্থতা আর একটি Rolling Deploy অনুসরণ করো।

পরিস্থিতি

TLS কোথায় শেষ, কে কী দেখে, আর কোন Box কাজটা করে।

ব্যবহারকারী
CDNEdge TLS + Cache
Object StorageStatic File
Load BalancerALB
app1Nginx + Gunicorn
app2Nginx + Gunicorn
app3Nginx + Gunicorn
RedisCache + Session
PostgreSQLPrimary
GET /dashboard

প্রতিটি Hop-এ Encryption

User → CDN

TLS 1.3, Public Certificate

CDN → LB

—

LB → App

—

ধাপ 1 / 5
1

ব্যবহারকারীর Browser নিকটতম CDN Edge-এর সঙ্গে TLS খোলে। প্রথম TLS Termination এখানেই হয়, ব্যবহারকারীর কাছে, তাতে Round Trip বাঁচে।

#যখন কিছু ভুল হয়

এই স্তরের বেশিরভাগ Incident হাতে গোনা কয়েকটি Status Code হিসেবে দেখা দেয়। কোনটা কে তৈরি করে শিখে নাও, তাহলে কোথায় খুঁজতে হবে বুঝবে।

লক্ষণমানে কীসাধারণ কারণসমাধান
502 Bad GatewayProxy Upstream থেকে কোনো বৈধ Response পায়নিApp Crash করেছে বা চালুই হয়নি; ভুল Port বা Socket Path; Connection refused; Unix Socket খোলার অনুমতি Nginx-এর নেই (Permission denied); Request-এর মাঝপথে App Worker মারা গেছে (যেমন Gunicorn Timeout, Memory শেষ); সব Upstream ব্যর্থ চিহ্নিত (no live upstreams)App চলছে কি না এবং proxy_pass যেখানে দেখায় সেখানে Listen করছে কি না দেখো; Nginx-এর Error Log ও App-এর নিজের Log পড়ো; Socket-এর Owner/Permission ঠিক করো
504 Gateway TimeoutUpstream গ্রহণ করেছে কিন্তু সময়মতো উত্তর দেয়নিধীর Database Query; বাইরের API ঝুলে আছে; Worker Pool শেষ, তাই Request সারিতেধীর অংশ খুঁজে বের করো (Log-এ $upstream_response_time); ঠিক করো বা Background Job-এ সরাও; কাজটা সত্যিই লম্বা হলে তবেই proxy_read_timeout বাড়াও
499 (শুধু Nginx Log-এ)Nginx উত্তর দেওয়ার আগেই Client Connection বন্ধ করেছেব্যবহারকারী হাল ছেড়েছে; মোবাইল Network কেটে গেছে; সামনের Load Balancer আগেই Timeout হয়েছেসাধারণত Upstream-এর ধীরগতির লক্ষণ; ওই লাইনগুলোর সময় দেখো
503 Service Unavailableইচ্ছাকৃত “এখন না”Maintenance Page; limit_req প্রত্যাখ্যান করছে (Default Status); HAProxy বা Cloud Load Balancer-এর কোনো সুস্থ Backend নেইHealth Check ও Rate Limit দেখো; আলাদা করে চিনতে limit_req_status 429 সেট করো
413 Request Entity Too LargeBody অনুমোদিত সীমার চেয়ে বড়Upload Endpoint-এ client_max_body_size (Default 1m); App ও Load Balancer-এরও সীমা থাকেসীমা বাড়াও নির্দিষ্ট location-এ, সবখানে নয়
403 ForbiddenServer পাঠাতে অস্বীকার করছেWorker User File পড়তে বা কোনো Parent Folder-এ ঢুকতে পারছে না; Index File ছাড়া Folder চাওয়া হয়েছে আর Listing বন্ধ (directory index ... is forbidden); deny RulePermission ঠিক করো (www-data-র জন্য Folder-এ Execute, File-এ Read লাগে); একটি Index File যোগ করো
আছে এমন File-এ 404 Not Foundভুল Path Mappingroot আর alias গুলিয়ে ফেলা; মিলে যাওয়া location-এ ভুল root; Fallback ছাড়া try_filesError Log-এ open() ... failed (2: No such file or directory) দেখো: ঠিক কোন Path চেষ্টা করা হয়েছে তা ছাপা থাকে
TLS Terminate করা Proxy-র পেছনে Redirect Loop (ERR_TOO_MANY_REDIRECTS)App ভাবে Request HTTP ছিল, তাই HTTPS-এ Redirect করে, অনন্তকালProxy App-এর সঙ্গে সাধারণ HTTP-তে কথা বলে আর X-Forwarded-Proto: https পাঠায় না, বা App তা বিশ্বাস করে নাHeader পাঠাও এবং Framework-কে Proxy বিশ্বাস করতে Configure করো (Django SECURE_PROXY_SSL_HEADER, Express trust proxy, Rails config.assume_ssl); CDN-এর TLS Mode-ও দেখো
Too many open files / worker_connections are not enoughFile Descriptor বা Connection Slot শেষপ্রতিটি Connection একটি File Descriptor (Proxy করলে দুটি); OS-এর সীমা (ulimit -n) worker_connections-এর প্রয়োজনের চেয়ে কমworker_rlimit_nofile আর Service-এর LimitNOFILE বাড়াও, তারপর worker_connections
ধীর Page, তারপর 504/502, CPU কমWorker Pool শেষসব PHP-FPM Child বা Gunicorn Worker ধীর Request-এ ব্যস্ত; নতুনগুলো সারিতেধীর Request খুঁজে বের করো; RAM অনুমতি দিলে তবেই Worker বাড়াও; Timeout দাও যাতে আটকে যাওয়া একটি Call চিরকাল একটি Worker ধরে না রাখে
প্রতিটি Response-এ Server: nginx/1.24.0Version ফাঁসDefault server_tokens on (Apache: ServerTokens Full)server_tokens off; (Apache: ServerTokens Prod, ServerSignature Off)

#Log পড়া

Access Log বলে কী ঘটেছে, Error Log বলে কেন। ওপরের Config-এর Log Format অনুযায়ী:

# /var/log/nginx/access.log
203.0.113.7 "GET /api/orders HTTP/2.0" 200 1532 rt=0.084 urt=0.081
203.0.113.7 "GET /assets/app.3f9a1c.js HTTP/2.0" 200 48211 rt=0.002 urt=-
198.51.100.4 "POST /api/upload HTTP/2.0" 413 183 rt=0.000 urt=-
192.0.2.55 "GET /api/report HTTP/2.0" 504 167 rt=30.001 urt=30.001

# /var/log/nginx/error.log
connect() failed (111: Connection refused) while connecting to upstream, upstream: "http://10.0.1.12:8000/api/orders"
upstream timed out (110: Connection timed out) while reading response header from upstream
client intended to send too large body: 52428800 bytes
connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream

urt=- মানে কোনো Upstream জড়িত ছিল না (Static File, বা শুরুতেই প্রত্যাখ্যান)। যে Request-এ rt urt-এর চেয়ে অনেক বড়, তার সময় গেছে ধীর Client-এর সঙ্গে কথা বলতে, App-এর অপেক্ষায় নয়। কাজের অভ্যাস: সমস্যা ঘটানোর সময় দুটো File-ই tail -f দিয়ে দেখো, awk দিয়ে Status Code গোনো (নিচে “নিজে করে দেখো” অংশ দেখো), আর প্রতিটি ব্যর্থ Upstream Call দেখতে grep upstream error.log।

#ধীর Client ও Slowloris

Slowloris Attack অনেকগুলো Connection খোলে এবং Request Header খুব ধীরে পাঠায়, কয়েক সেকেন্ড পরপর কয়েক Byte, কখনো শেষ করে না। যে Server প্রতি Connection-এ একটি Process বা Thread রাখে (Apache prefork, আর কিছুটা কম worker), কয়েকশো এমন Connection-এই তার Worker শেষ হয়ে যায়, অথচ আক্রমণকারীর Bandwidth প্রায় লাগেই না। Event-driven Server (Nginx, অলস Keep-alive-এর জন্য Apache event, Caddy, HAProxy) প্রতিটি অপেক্ষমাণ Connection-কে একটি ছোট Structure হিসেবে রাখে, তাই আক্রমণে তার খরচ সামান্য; Timeout (client_header_timeout, client_body_timeout, Apache-এর mod_reqtimeout) আর IP প্রতি Connection-সীমা বাকিটা বন্ধ করে। একই কারণে সামনে একটি Proxy থাকলে প্রতিটি App Server উপকৃত হয়: ধীর অংশটা সে-ই সামলায়।

নিজেকে যাচাই করোপ্রশ্ন

ব্যবহারকারীরা 502 Bad Gateway দেখছে। Nginx Error Log বলছে “connect() failed (111: Connection refused) while connecting to upstream”। সম্ভাব্য কারণ কী?

#Performance ও নিরাপত্তার Checklist

Performance

  • Client-এর জন্য (keepalive_timeout) ও Upstream-এর জন্য (upstream Block-এ keepalive সঙ্গে proxy_http_version 1.1) Keep-alive চালু রাখো, যাতে Connection ও TLS Handshake আবার ব্যবহার হয়।
  • Compression: Text Type-এর জন্য gzip বা brotli; gzip_static দিয়ে আগে থেকে Compress করা File।
  • Caching Header: Fingerprint করা Asset-এর জন্য immutable-সহ লম্বা Cache-Control, HTML-এর জন্য ছোট বা no-cache; Static File-এর জন্য সামনে একটি CDN।
  • Static File-এর জন্য sendfile ও tcp_nopush; অনেক ছোট File-ওয়ালা ব্যস্ত Site-এ open_file_cache।
  • সবখানে HTTP/2, যেখানে Client উপকৃত হয় সেখানে HTTP/3 (মোবাইল, Packet হারানো Network)।
  • TLS: শুধু TLS 1.2 ও 1.3, আধুনিক Cipher, Session Resumption, সবখানে HTTPS চালু হয়ে গেলে HSTS।
  • ঠিক মাপের Pool: worker_processes auto, File Descriptor সীমা, Memory ও CPU দেখে App Worker-এর সংখ্যা, Load Test দিয়ে মেপে নেওয়া।

নিরাপত্তা

  • অধিকারহীন Worker: শুধু Master root হিসেবে চলে; App Server চলে তাদের নিজস্ব User হিসেবে।
  • Version লুকাও: server_tokens off, ServerTokens Prod, App থেকে X-Powered-By সরাও।
  • Request-এর সীমা: client_max_body_size, Header-এর আকারের সীমা, Login ও ব্যয়বহুল Endpoint-এ limit_req, IP প্রতি limit_conn।
  • Timeout: ধীর Client-এর বিরুদ্ধে ছোট Header ও Body Timeout; স্তরে স্তরে সাজানো Upstream Timeout।
  • Directory Listing বন্ধ (autoindex off, Options -Indexes) এবং লুকানো File নিষিদ্ধ (location ~ /\. { deny all; }, তবে /.well-known/ খোলা রাখো)।
  • Security Header: X-Content-Type-Options, Content-Security-Policy, Referrer-Policy, HSTS।
  • Forwarded Header বিশ্বাস করো শুধু নিজের Proxy থেকে (Nginx set_real_ip_from, Apache mod_remoteip)।
  • হালনাগাদ রাখো, আর প্রতিটি Config পরিবর্তন Graceful Reload-এর আগে Test করো (nginx -t, apachectl configtest)।

#System Design-এ Web ও App Server

Interview-তে Request-এর পথ আঁকো এবং প্রতিটি Box-এর নাম দাও তার ভূমিকা দিয়ে:

Client ─► DNS ─► CDN (Static File, Edge TLS, Cache)
       ─► Load Balancer (TLS Termination, Health Check)          ┐ Stateless,
       ─► Web Tier: Nginx (Static, Buffering, সীমা, Routing)      │ LB-এর পেছনে
       ─► App Tier: তোমার Code চালানো App Server Worker           ┘ Scale Out
       ─► Cache (Redis) ও Database (Primary + Replica)

যে কথাগুলো জোরে বলা উচিত:

  • Web ও App Tier Stateless রাখো (Session Redis বা Token-এ, File Object Storage-এ), যাতে Load Balancer-এর পেছনে ইচ্ছেমতো Machine যোগ বা বাদ দেওয়া যায়। এটাই Horizontal Scaling: আরও বড় একটি নয়, আরও কয়েকটি একই রকম App Server।
  • প্রতি Box-এর Concurrency ঠিক করে App Server-এর Worker। মোটামুটি ক্ষমতার হিসাব: Worker × Machine ÷ গড় Request-এর সময়; প্রতিটি ১০০ মিলিসেকেন্ড নিলে ৮টি Worker প্রতি Machine-এ সেকেন্ডে প্রায় ৮০টি Request সামলায়। Request বেশিরভাগ সময় I/O-র অপেক্ষায় থাকলে Async Server বা বেশি Thread এই সংখ্যা বাড়ায়; CPU খাটলে শুধু বেশি Core-ই সাহায্য করে।
  • App-এর যা দেখা উচিত না, সামনের স্তর তা শুষে নেয়: ধীর Client, TLS, Static File, অপব্যবহারকারী Traffic। এজন্যই Web Tier-এ App Tier-এর চেয়ে অনেক কম Machine লাগে।
  • Single Point of Failure সরাও: অন্তত দুটি Load Balancer Node (বা একাধিক Zone জুড়ে একটি Managed), অন্তত দুটি App Server, আসল Dependency যাচাই করা Health Check, আর Graceful Shutdown, যাতে Deploy Connection কেটে না দিয়ে শেষ করতে দেয়।
  • Deploy: Rolling (একবারে কয়েকটি Instance বদলানো), Blue-green (Load Balancer-কে নতুন একটি বহরে ঘুরিয়ে দেওয়া) বা Canary (আগে নতুন Version-কে ছোট একটি Weight দেওয়া)। সবই নির্ভর করে তোমার Configure করা Load Balancer ও Health Check-এর ওপর।

#নিজে করে দেখো

Docker-এ Nginx চালাও এবং তার Response Header দেখো:

docker run --rm -d --name web -p 8080:80 nginx
curl -I http://localhost:8080
#   HTTP/1.1 200 OK
#   Server: nginx/1.x.y         <- Default-ভাবে Version ফাঁস হয়
#   Content-Type: text/html
docker exec web nginx -T | head -40     # সম্পূর্ণ সক্রিয় Config দেখাও
docker logs -f web                       # Image-এ Access Log stdout-এ যায়

একটি ন্যূনতম Config যা একটি Folder পাঠায় আর /api/ Port 3000-এর একটি App-এ Proxy করে:

# site.conf
server {
    listen 80;
    root /usr/share/nginx/html;
    location / {
        try_files $uri $uri/ =404;
    }
    location /api/ {
        proxy_pass http://host.docker.internal:3000;   # Host Machine (Linux: --add-host=host.docker.internal:host-gateway যোগ করো)
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
docker run --rm -d --name web2 -p 8081:80 \
  -v "$PWD/site.conf:/etc/nginx/conf.d/default.conf:ro" nginx
docker exec web2 nginx -t          # Config Test
docker exec web2 nginx -s reload   # site.conf বদলানোর পর Graceful Reload

Caddy দিয়ে একই Reverse Proxy, HTTPS-সহ (localhost-এর জন্য Caddy নিজের Local Certificate Authority ব্যবহার করে):

localhost {
    reverse_proxy :3000
}
caddy run    # Caddyfile যে Folder-এ আছে সেখানে

Production পদ্ধতিতে একটি Python App চালাও:

pip install gunicorn uvicorn
gunicorn -w 4 -b 127.0.0.1:8000 app:app      # WSGI (Flask/Django), ৪টি Worker Process
uvicorn main:app --workers 4 --port 8000     # ASGI (FastAPI)

একটি দ্রুত Load Test, তারপর Static File আর Dynamic Endpoint তুলনা করো:

hey -n 5000 -c 100 http://localhost:8080/            # Nginx থেকে Static File
hey -n 5000 -c 100 http://localhost:8081/api/items   # তোমার App-এ Proxy করা
ab -n 5000 -c 100 http://localhost:8080/             # ApacheBench দিয়ে একই কাজ

সেকেন্ডে কতগুলো Request আর Latency-র Percentile তুলনা করো: Static File সাধারণত অনেক দ্রুত, আর সেই পার্থক্যটাই তোমার Code চালানোর খরচ। আসল Server-এ Test চলার সময় Log দেখো:

sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn   # Status Code (Default "combined" Format)

#সারসংক্ষেপ

মূল কথাগুলো

  • Web Server HTTP-তে কথা বলে: গ্রহণ (ও TLS), পার্স, সিদ্ধান্ত (File, App, Redirect, প্রত্যাখ্যান), Response, Log। Static File সে নিজেই পাঠায়, Dynamic Request পাঠায় এমন একটি Application Server-এ যা তোমার Code চালায়। Reverse Proxy ও Load Balancer হলো ভূমিকা, যা প্রায়ই একই Software পালন করে।
  • Apache: Virtual Host, Directory Rule, Module, আর .htaccess (Shared Hosting-এ সুবিধাজনক, প্রতিটি Request-এ পড়া হয় বলে ধীর)। এর MPM Concurrency ঠিক করে: prefork (প্রতি Connection-এ Process, mod_php-এর জন্য দরকার), worker (Thread), event (Thread, সঙ্গে অলস Keep-alive-এর জন্য একটি Listener)।
  • Nginx: root Master Port Bind করে এবং অধিকারহীন Worker সামলায়, প্রতি Core-এ একটি, প্রত্যেকে হাজার হাজার Non-blocking Connection-এর ওপর একটি Event Loop, তাই Memory প্রায় স্থির। Config হলো http → server → location (Exact, ^~, ক্রমানুসারে Regex, সবচেয়ে লম্বা Prefix), সঙ্গে try_files, proxy_pass, upstream, proxy_cache, limit_req। nginx -t তারপর nginx -s reload একটি Connection-ও না ফেলে Worker বদলায়।
  • Caddy ACME দিয়ে নিজেই Certificate আনে ও নবায়ন করে (HTTP-01-এর জন্য DNS তার দিকে আর Port 80 খোলা থাকতে হয়)। Windows ও ASP.NET-এর জন্য IIS, .htaccess-সামঞ্জস্যপূর্ণ Apache-বিকল্প হিসেবে LiteSpeed; অন্য ভূমিকাগুলো নেয় HAProxy, Envoy, Traefik, Varnish ও Cloud Load Balancer।
  • App Server-এর Interface: CGI (প্রতি Request-এ Process) → FastCGI / PHP-FPM (Pool, pm.max_children), WSGI (Gunicorn) ও ASGI (Uvicorn), Rack (Puma), Servlet (Tomcat, প্রায়ই Spring Boot-এ Embed করা), Kestrel; Node.js ও Go App নিজেরাই Server।
  • Code কে তৈরি করেছে দেখে Debug করো: 502 (Upstream বন্ধ/প্রত্যাখ্যাত/Crash), 504 (Upstream খুব ধীর), 499 (Client হাল ছেড়েছে), 503 (সুস্থ Backend নেই, Maintenance বা Rate Limit), 413 (client_max_body_size), 403 (Permission/Index), Redirect Loop (X-Forwarded-Proto)। Error Log কারণ বলে দেয়; Timeout বাইরে থেকে ভেতরে স্তরে স্তরে সাজাতে হয়।
  • System Design-এ: CDN → Load Balancer → Stateless Web Tier → Stateless App Tier → Cache ও Database, Horizontal-ভাবে Scale করা, Health Check ও Graceful Deploy-সহ।