nginx reverse proxy কনফিগারেশন করার সঠিক নিয়ম
Ubuntu 24.04-এ nginx reverse proxy সেটআপ করার সম্পূর্ণ গাইড। proxy_pass, প্রয়োজনীয় চারটি হেডার, websockets এবং ফাইল আপলোড কনফিগারেশনের লাইন বাই লাইন ব্যাখ্যা এখানে দেওয়া হলো।
nginx reverse proxy কনফিগারেশন যা করে
একটি nginx reverse proxy পোর্ট 80 এবং 443-এ আসা অনুরোধগুলো গ্রহণ করে এবং প্রতিটি অনুরোধকে লোকাল পোর্টে চলমান অ্যাপ্লিকেশনের কাছে পাঠিয়ে দেয়। এরপর অ্যাপ্লিকেশনটি যে উত্তর দেয়, তা ব্রাউজারে ফেরত পাঠায়। এর কনফিগারেশনটি একটি মাত্র server ব্লকের মাধ্যমে করা হয় এবং এই ব্লকটি বেশ সংক্ষিপ্ত। এর মূল জটিলতা মূলত পাঁচ বা ছয়টি লাইনের মধ্যে সীমাবদ্ধ, যা আপনার অ্যাপকে জানায় যে প্রকৃত ক্লায়েন্ট কে ছিল এবং ক্লায়েন্ট কোন প্রোটোকল ব্যবহার করেছে।
নিচের সবকিছু Ubuntu 24.04-এ ডিস্ট্রিবিউশনের নিজস্ব nginx প্যাকেজ ব্যবহার করে তৈরি করা হয়েছে। এর শুরুর বিন্দু হলো এমন একটি অ্যাপ যা ইতিমধ্যে 127.0.0.1:3000-এ সাড়া দিচ্ছে। আপনি যদি এখনও কোনো প্রক্সি নির্বাচন না করে থাকেন, তবে nginx বনাম Caddy এবং Traefik-এর তুলনা নিবন্ধটি আগে পড়ে নিতে পারেন। নিচে nginx-এর কনফিগারেশন লাইন বাই লাইন ব্যাখ্যা করা হলো।
এই কনফিগারেশনগুলো আপনার নিজের সার্ভারে চালান। প্রতিটি পরিবর্তনের পর রিলোড করার আগে sudo nginx -t দিয়ে পরীক্ষা করুন এবং আউটপুটটি মনোযোগ দিয়ে পড়ুন।
Ubuntu-তে Nginx কনফিগারেশন কোথায় থাকে
sudo apt update
sudo apt install -y nginx
ls -l /etc/nginx/sites-enabled/মূল ফাইলটি হলো /etc/nginx/nginx.conf। এটি একটি http { } ব্লকের ভেতরে গ্লোবাল অপশনগুলো সেট করে এবং এরপর দুটি ডিরেক্টরিকে অন্তর্ভুক্ত করে: /etc/nginx/conf.d/*.conf এবং /etc/nginx/sites-enabled/*। Ubuntu এবং Debian-এ আপনি প্রতিটি সাইটের জন্য /etc/nginx/sites-available/-এ একটি করে ফাইল তৈরি করেন এবং /etc/nginx/sites-enabled/-এ একটি symlink তৈরির মাধ্যমে তা চালু করেন। symlink মুছে ফেললে সাইটটি নিষ্ক্রিয় হয়ে যায়, কিন্তু ফাইলটি থেকে যায়।
পরবর্তীতে ব্যবহৃত দুটি ডিরেক্টিভ শুধুমাত্র http কনটেক্সটেই কাজ করে, কখনোই server ব্লকের ভেতরে নয়: map এবং upstream। এগুলোকে /etc/nginx/conf.d/-এর অধীনে আলাদা ফাইলে রাখুন, কারণ সেই ডিরেক্টরিটি http লেভেলে অন্তর্ভুক্ত করা হয়।
প্যাকেজটি default নামে একটি সক্রিয় সাইট নিয়ে আসে। এটি default_server হিসেবে চিহ্নিত, যার অর্থ হলো এটি এমন যেকোনো অনুরোধের উত্তর দেয় যার Host হেডার আপনার কনফিগারেশনের কোথাও থাকা কোনো server_name-এর সাথে মেলে না। এটি সক্রিয় থাকলে, আপনার ডোমেইন নামের সাথে না মেলা অনুরোধগুলো আপনার অ্যাপের পরিবর্তে এই সাইটে চলে যায়। আপনার নিজস্ব সাইট কাজ করা শুরু করলে symlink-টি মুছে ফেলুন।
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginxএকটি অ্যাপ প্রক্সি করার জন্য ক্ষুদ্রতম সার্ভার ব্লক
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}এটিকে /etc/nginx/sites-available/app.example.com হিসেবে সেভ করুন, তারপর এটি enable করুন এবং লোড করুন।
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl -sI -H 'Host: app.example.com' http://127.0.0.1/listen 80; IPv4-কে bind করে এবং listen [::]:80; IPv6-কে bind করে। দ্বিতীয় লাইনটি বাদ দিলে, যে ভিজিটরের DNS (domain name system) লুকআপ আপনার সার্ভারের জন্য একটি AAAA রেকর্ড প্রদান করে, সে connection refused ত্রুটি পাবে, অথচ IPv4 ব্যবহারকারীরা সাইটটি সচল দেখতে পাবে। আপনি যে বাগ রিপোর্টটি পাবেন তাতে লেখা থাকবে "it works for me"।
server_name ব্রাউজারের পাঠানো Host হেডারের সাথে মেলানো হয়। স্পেস দিয়ে আলাদা করে একাধিক নাম এখানে তালিকাভুক্ত করা যেতে পারে। যদি কোনো ব্লক না মেলে, তবে Nginx সেই ব্লকটি ব্যবহার করে যেটি default_server, আর এই কারণেই প্যাকেজ করা সাইটটি সরিয়ে ফেলতে হয়েছে।
location / হলো রিকোয়েস্ট পাথের একটি প্রিফিক্স ম্যাচ এবং / প্রতিটি পাথকে ম্যাচ করে। proxy_pass হলো সেই ঠিকানা যেখানে Nginx সংযোগ খোলে। অ্যাপটিকে 127.0.0.1-এ bind করে রাখুন যাতে Nginx ছাড়া অন্য কোনো পথে ঢোকা না যায়। যদি অ্যাপটি কন্টেইনারে চলে, তবে এটিকে 127.0.0.1:3000:3000 হিসেবে পাবলিশ করুন, 3000:3000 হিসেবে নয়, কারণ Docker নিজস্ব রুল লেখে এবং ufw-কে উপেক্ষা করে সরাসরি পোর্ট পাবলিশ করে, তাই আপনার ফায়ারওয়াল যাই বলুক না কেন, সরাসরি পাবলিশ করা পোর্ট ইন্টারনেট থেকে অ্যাক্সেসযোগ্য থাকে।
curl লাইনটি সার্ভার থেকে সঠিক Host হেডার পাঠায়, যাতে DNS কোথাও পয়েন্ট করার আগেই আপনি ব্লকটি পরীক্ষা করতে পারেন।
আপনি যখন অন্য কিছু লেখেন না তখন nginx আপস্ট্রিমের কাছে যা পাঠায়
proxy_pass নিজে থেকে আপনার অ্যাপ্লিকেশনের চারটি তথ্য গোপন করে।
nginx ডিফল্টভাবে ব্যাকএন্ডের সাথে HTTP/1.0 প্রোটোকলে কথা বলে এবং Connection: close পাঠায়, তাই প্রতিটি অনুরোধ একটি নতুন আপস্ট্রিম সংযোগ খোলে এবং কোনো প্রোটোকল আপগ্রেড সম্ভব হয় না।
Host হেডারটি পুনরায় লিখে proxy_pass-এর মানে পরিবর্তন করা হয়, যা হলো 127.0.0.1:3000। যে অ্যাপ Host থেকে অ্যাবসোলিউট লিঙ্ক তৈরি করে, সেটি এখন এমন লিঙ্ক তৈরি করে যা সার্ভারের বাইরের কেউ খুলতে পারে না।
অ্যাপের কাছে পৌঁছানো সংযোগটি nginx থেকে আসে, তাই অ্যাপটি ক্লায়েন্টের ঠিকানা হিসেবে 127.0.0.1 দেখে। এর ফলে অ্যাপের ভেতরের প্রতিটি লগ লাইন এবং রেট লিমিট ভিজিটরের পরিবর্তে প্রক্সিকে রেকর্ড করে।
অ্যাপটি বুঝতে পারে না যে ব্রাউজারটি HTTPS ব্যবহার করেছে, কারণ এটি যে সংযোগটি পেয়েছে তা লুপব্যাক ঠিকানায় সাধারণ HTTP।
চারটি লাইন এই সব সমস্যার সমাধান করে।
যে চারটি হেডার সেট করতে হবে এবং প্রতিটি ব্যাকএন্ডকে কী দেখতে দেয়
location / {
proxy_pass http://127.0.0.1:3000;
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;
}Host ভিজিটর যে নাম টাইপ করেছে তা বহন করে। $host হলো রিকোয়েস্ট থেকে পাওয়া নাম, যার পোর্ট সরিয়ে ফেলা হয়েছে এবং অক্ষরগুলো ছোট হাতের করা হয়েছে। এটি সেট করুন যাতে আপনার অ্যাপ সঠিক absolute URL তৈরি করতে পারে: যেমন লগইনের পরের রিডাইরেক্ট বা পাসওয়ার্ড রিসেট ইমেইলের ভেতরের লিঙ্ক। এটি বাদ দিলে সেই URL-গুলো 127.0.0.1:3000-এর দিকে নির্দেশ করবে, ফলে লগইন করার সময় ব্রাউজার এমন একটি ঠিকানায় যাবে যা কানেকশন প্রত্যাখ্যান করবে। যদি আপনার অ্যাপের পোর্টেরও প্রয়োজন হয়, কারণ আপনি এটি 8080 পোর্টে সার্ভ করছেন, তবে $http_host ব্যবহার করুন, যা ক্লায়েন্ট ঠিক যেভাবে পাঠিয়েছে সেই হেডারটিই বহন করে।
X-Real-IP একটি মান বহন করে: $remote_addr, যে ঠিকানা থেকে nginx কানেকশন গ্রহণ করেছে। অ্যাপগুলো তাদের নিজস্ব এক্সেস লগ এবং রেট লিমিটিংয়ের জন্য এটি পড়ে।
X-Forwarded-For একটি তালিকা বহন করে। $proxy_add_x_forwarded_for ক্লায়েন্ট ইতিমধ্যে সেই হেডারে যা দিয়েছে তার সাথে $remote_addr যুক্ত করে, তাই মানটি কমা দিয়ে আলাদা করা থাকে এবং আপনার nginx যে এন্ট্রি যোগ করেছে তা সবার শেষে থাকে। এই বিষয়টি নির্ধারণ করে যে হেডারটি বিশ্বাসযোগ্য কি না: একজন ক্লায়েন্ট তার পছন্দমতো যেকোনো X-Forwarded-For পাঠাতে পারে, তাই যে অ্যাপ প্রথম এন্ট্রিটি পড়ে তাকে যেকোনো ভুল ঠিকানা দেখানো সম্ভব। যখন nginx এজ সার্ভার হিসেবে কাজ করে, তখন $remote_addr লিখুন এবং ক্লায়েন্টের সংস্করণটি বাতিল করুন। যখন সামনে কোনো CDN বা অন্য কোনো প্রক্সি থাকে, তখন realip মডিউল থেকে set_real_ip_from এবং real_ip_header ব্যবহার করুন, যাতে $remote_addr নিজেই প্রকৃত ক্লায়েন্ট ঠিকানা হয়ে ওঠে।
X-Forwarded-Proto http অথবা https বহন করে। ফ্রেমওয়ার্কগুলো এটি পড়ে সিদ্ধান্ত নেয় যে কুকিগুলোকে Secure হিসেবে চিহ্নিত করবে কি না এবং HTTPS-এ রিডাইরেক্ট করতে বাধ্য করবে কি না। একটি TLS সাইটে এটি বাদ দিলে এবং অ্যাপটি HTTPS-এ বাধ্য করার জন্য কনফিগার করা থাকলে, সেটি http দেখতে পায়, HTTPS ঠিকানায় রিডাইরেক্ট দিয়ে উত্তর দেয়, nginx-এর মাধ্যমে পরবর্তী রিকোয়েস্ট পায়, তখনও http দেখে এবং পুনরায় রিডাইরেক্ট করে। ব্রাউজার তখন হাল ছেড়ে দেয় এবং ERR_TOO_MANY_REDIRECTS দেখায়।
প্রতিটি location-এ এই চারটি লাইন বারবার লেখা হলে সেগুলোর মধ্যে অমিল তৈরি হয়। এগুলোকে একটি ফাইলে রাখুন এবং include করুন।
# /etc/nginx/snippets/proxy-headers.conf
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;location / {
include snippets/proxy-headers.conf;
proxy_pass http://127.0.0.1:3000;
}এখানে ইনহেরিটেন্স বা উত্তরাধিকারের একটি ফাঁদ আছে। একটি location তার server ব্লক থেকে proxy_set_header ডিরেক্টিভগুলো কেবল তখনই ইনহেরিট করে যখন সেই location-এ নিজস্ব কোনো ডিরেক্টিভ সংজ্ঞায়িত থাকে না। location-এর ভেতরে একটি proxy_set_header যোগ করলেই server লেভেলে সংজ্ঞায়িত সমস্ত হেডার সেই location-এর জন্য বাদ পড়ে যায়। তাই সবগুলো একই লেভেলে রাখুন, অথবা প্রতিটি প্রক্সি করা location-এ স্নিপেটটি include করুন।
আমার WebSocket অ্যাপটি কানেক্ট হওয়ার পর কেন ডিসকানেক্ট হয়ে যায়?
ডিফল্ট সেটিংস আপগ্রেড করার অনুমতি দেয় না এবং ডিফল্ট রিড টাইমআউট 60 সেকেন্ড পর অলস টানেল বন্ধ করে দেয়। একটি WebSocket শুরু হয় Upgrade: websocket এবং Connection: Upgrade বহনকারী একটি HTTP রিকোয়েস্ট হিসেবে। এগুলো হলো hop-by-hop হেডার, যার অর্থ হলো প্রক্সিকে এগুলো পাস না করে বরং গ্রহণ করতে হয় এবং HTTP/1.0-এ আপগ্রেড করার কোনো মেকানিজম নেই। তাই উভয়কেই ম্যানুয়ালি পুনরায় যুক্ত করতে হয়।
ম্যাপটি http কনটেক্সটে, নিজস্ব ফাইলে রাখতে হয়।
# /etc/nginx/conf.d/websocket.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}এরপর লোকেশন ব্লক।
location / {
include snippets/proxy-headers.conf;
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}ম্যাপটি থাকার কারণ হলো যাতে একটি লোকেশন উভয় ধরনের ট্রাফিক হ্যান্ডেল করতে পারে। সাধারণ রিকোয়েস্টের ক্ষেত্রে $http_upgrade খালি থাকে, তাই $connection_upgrade হয়ে যায় close। আপগ্রেড রিকোয়েস্টের ক্ষেত্রে এটি websocket ধারণ করে, তাই আপস্ট্রিমের কাছে পাঠানো হেডারটি হয় Connection: upgrade। proxy_set_header Connection "upgrade"; হার্ড-কোড করলে তা প্রতিটি সাধারণ পেজ রিকোয়েস্টেও হেডারটি পাঠিয়ে দেয় এবং কিছু ব্যাকএন্ড এমন রিকোয়েস্টের উত্তরে 400 এরর দেয়।
proxy_read_timeout হলো সেই প্যারামিটার যা "এটি লোড হয়, তারপর আপডেট হওয়া বন্ধ হয়ে যায়" এমন রিপোর্টের কারণ। এর ডিফল্ট মান 60 সেকেন্ড এবং এটি ব্যাকএন্ড থেকে দুটি রিডের মধ্যবর্তী সময় পরিমাপ করে, কানেকশনের মোট সময়কাল নয়। একটি WebSocket যদি 60 সেকেন্ড পর্যন্ত নিষ্ক্রিয় থাকে, তবে Nginx তা বন্ধ করে দেয় এবং ব্রাউজার কনসোলে 1006 কোডসহ সকেট বন্ধ হওয়ার বার্তা দেখা যায়। যে অ্যাপগুলো প্রতি মিনিটে অন্তত একবার নিজস্ব হার্টবিট পাঠায়, তারা এটি বুঝতে পারে না। যারা পাঠায় না, তারা এক মিনিট পরেই বন্ধ হয়ে যায়। লাইভ এডিটর এবং ড্যাশবোর্ডের ক্ষেত্রে এটি প্রথম দেখা যায়, যার একটি সাধারণ উদাহরণ হলো HTTPS-এর পেছনে থাকা একটি self-hosted n8n instance।
কেন proxy_pass-এ একটি ট্রেইলিং স্ল্যাশ আমার URL-গুলোকে পরিবর্তন করে?
নিয়মটি এক বাক্যের। যদি proxy_pass একটি URI (uniform resource identifier) দিয়ে শেষ হয়, এমনকি একটি সাধারণ / হলেও, Nginx অনুরোধের সেই অংশটি মুছে ফেলে যা location প্রিফিক্সের সাথে মিলেছিল এবং তার জায়গায় ওই URI বসিয়ে দেয়। যদি proxy_pass হোস্ট এবং পোর্টে গিয়ে থেমে যায়, তবে অনুরোধের পাথটি অপরিবর্তিত অবস্থায় পাস করা হয়।
location /app/ {
proxy_pass http://127.0.0.1:3000/;
}/app/status-এর জন্য একটি অনুরোধ ব্যাকএন্ডে /status হিসেবে পৌঁছায়।
location /app/ {
proxy_pass http://127.0.0.1:3000;
}/app/status-এর জন্য একটি অনুরোধ ব্যাকএন্ডে /app/status হিসেবে পৌঁছায়।
আপনার কোন ফরম্যাটটি প্রয়োজন তা নির্ভর করে অ্যাপের ওপর। একটি বেস-পাথ বা সাব-ফোল্ডার সেটিং থাকা অ্যাপ দ্বিতীয় ফরম্যাটটি চায়, যেখানে সেটিংটিকে /app সম্পর্কে জানানো থাকে। যে অ্যাপ প্রিফিক্স সম্পর্কে কিছুই জানে না, তার জন্য প্রথমটি প্রয়োজন। প্রথম ফরম্যাটের একটি তাৎক্ষণিক অসুবিধা হলো: অ্যাপটি যে HTML রিটার্ন করে তাতে এখনো /static/main.css-এর মতো অ্যাবসোলিউট পাথ থাকে, ব্রাউজার সেগুলোর জন্য সাইট রুটে অনুরোধ পাঠায়, কোনো লোকেশন ম্যাচ করে না এবং পেজটি কোনো স্টাইলিং ছাড়াই রেন্ডার হয়। ব্রাউজারের নেটওয়ার্ক ট্যাবে দেখা যায় যে সেই অ্যাসেট অনুরোধগুলো 404 হিসেবে ফিরে আসছে। এর সমাধান হলো অ্যাপের নিজস্ব বেস-পাথ সেটিং, অথবা একই ব্যাকএন্ডের দিকে নির্দেশ করা একটি দ্বিতীয় location /static/।
একটি রেজেক্স (regex) লোকেশন proxy_pass-এ কোনো URI বহন করতে পারে না। sudo nginx -t কনফিগারেশনটি প্রত্যাখ্যান করে এবং কারণটি উল্লেখ করে: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block।
এই ধরনের সব সমস্যা দূর হয়ে যায় যখন প্রতিটি অ্যাপ তার নিজস্ব নাম, app.example.com পায়, যা location / থেকে প্রক্সি করা হয়। সাব-পাথ তখনই ব্যবহার করা উচিত যখন আপনি DNS রেকর্ড যোগ করতে পারেন না।
আমি কীভাবে একটি নামের পেছনে একাধিক backend রাখতে পারি?
একটি upstream ব্লকের মাধ্যমে এটি করা যায়। এটি http কনটেক্সটের অন্তর্ভুক্ত, তাই একই ফাইলে server ব্লকের উপরে অথবা /etc/nginx/conf.d/-এ এটি লিখুন।
upstream app_backend {
least_conn;
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
keepalive 32;
}এরপর location-টি এটিকে এভাবে নির্দেশ করে: proxy_pass http://app_backend;।
ডিফল্ট পদ্ধতি হলো round robin। least_conn প্রতিটি অনুরোধকে সেই backend-এ পাঠায় যেখানে সক্রিয় সংযোগের সংখ্যা সবচেয়ে কম, যা অসম দৈর্ঘ্যের অনুরোধের জন্য উপযুক্ত। ip_hash একটি ক্লায়েন্ট অ্যাড্রেসকে একটি নির্দিষ্ট backend-এর সাথে যুক্ত করে। আপনার ip_hash প্রয়োজন হবে যখন অ্যাপটি তার নিজস্ব মেমরিতে সেশন ধরে রাখে, কারণ দুটি backend-এর মধ্যে round robin ব্যবহার করলে ব্যবহারকারীরা হঠাৎ লগ-আউট হয়ে যেতে পারেন, কারণ তাদের অনুরোধ এমন একটি ইনস্ট্যান্সে পৌঁছায় যেখানে তাদের সেশন নেই। সেশনগুলোকে শেয়ারড স্টোরেজে স্থানান্তর করাই এর উত্তম সমাধান।
max_fails=3 fail_timeout=30s মানে হলো 30 সেকেন্ডের মধ্যে তিনটি ব্যর্থ প্রচেষ্টার পর সেই সার্ভারটিকে 30 সেকেন্ডের জন্য নিষ্ক্রিয় করে দেওয়া হবে। যখন ব্লকের প্রতিটি সার্ভার এই অবস্থায় থাকে, তখন ক্লায়েন্টরা 502 ত্রুটি পায় এবং এরর লগে no live upstreams while connecting to upstream লেখা থাকে।
keepalive 32 প্রতি ওয়ার্কার প্রসেসে backend-এর জন্য 32টি পর্যন্ত নিষ্ক্রিয় সংযোগ খোলা রাখে, যা অধিকাংশ অনুরোধ থেকে TCP হ্যান্ডশেক প্রক্রিয়াটি সরিয়ে দেয়। এটি শুধুমাত্র proxy_http_version 1.1-এর সাথে কাজ করে এবং আপস্ট্রিমে কোনো Connection: close থাকে না। যদি একই location-এ WebSocket ম্যাপও ব্যবহৃত হয়, তবে খালি কেসটিকে close থেকে একটি খালি স্ট্রিংয়ে পরিবর্তন করুন, যাতে সাধারণ অনুরোধগুলোতে কোনো Connection হেডার না থাকে এবং পুল করা সংযোগটি পুনরায় ব্যবহার করা যায়।
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}একটি upstream ব্লকের ভেতরের নামগুলো nginx চালু হওয়ার সময় রিজলভ হয়। যদি আপনার backend একটি কন্টেইনার হয় যা রিস্টার্টের সময় নতুন অ্যাড্রেস পায়, তবে nginx পুরনো অ্যাড্রেসটিই ব্যবহার করতে থাকে যতক্ষণ না আপনি এটিকে রিলোড করেন। Docker নেটওয়ার্কের ভেতরে আপনি এমবেডেড রিজলভার ব্যবহার করে লুকআপটিকে অনুরোধের সময়ে স্থানান্তর করতে পারেন।
resolver 127.0.0.11 valid=10s;
set $backend http://app:3000;
proxy_pass $backend;যখন কন্টেইনারগুলো এত ঘন ঘন তৈরি ও মুছে ফেলা হয় যে আপনাকে তাল মিলিয়ে চলার জন্য nginx এডিট করতে হয়, তখন কন্টেইনার লেবেল পড়তে পারে এমন একটি প্রক্সি ব্যবহার করাই উত্তম। কয়েকটি Docker Compose অ্যাপের সামনে Traefik কন্টেইনারগুলো থেকেই তার রুট তৈরি করে নেয়।
কেন আপলোড করার সময় 413 Request Entity Too Large ত্রুটি দেখা দেয়?
client_max_body_size ডিফল্টভাবে 1 মেগাবাইটে সেট করা থাকে। আপনার অ্যাপ কোনো অনুরোধ পাওয়ার আগেই nginx বড় সাইজের রিকোয়েস্ট বডি প্রত্যাখ্যান করে এবং এরর লগে client intended to send too large body রেকর্ড করে। সার্ভার ব্লকে অথবা যেখানে আপলোড হচ্ছে সেই লোকেশন ব্লকে এর মান বাড়িয়ে দিন।
client_max_body_size 512m;0 মানটি সেট করলে এই চেকটি পুরোপুরি বন্ধ হয়ে যায়। অ্যাপের নিজস্ব লিমিটও থাকে, তাই এই পরিবর্তনের পরেও যদি 413 ত্রুটি থেকে যায়, তবে বুঝতে হবে সেটি backend থেকে আসছে এবং সেক্ষেত্রে অ্যাপের নিজস্ব আপলোড সেটিং পরীক্ষা করতে হবে।
ডিফল্টভাবে nginx আপস্ট্রিম কানেকশন খোলার আগেই পুরো রিকোয়েস্ট বডি পড়ে নেয় এবং বড় ফাইলগুলো প্রথমে ডিস্কে টেম্পোরারি ফাইলে লিখে রাখে। এটি অ্যাপকে ধীরগতির ক্লায়েন্ট থেকে সুরক্ষা দেয়, কারণ backend তখন স্থানীয় গতিতে আপলোড গ্রহণ করতে পারে। খুব বড় আপলোডের ক্ষেত্রে আপনি চাইলে স্ট্রিমিং ব্যবহার করতে পারেন।
proxy_request_buffering off;সেক্ষেত্রে backend বডি আসার সাথে সাথেই তা গ্রহণ করে এবং সেটি হ্যান্ডেল করার সক্ষমতা থাকতে হয়। nginx তখন অন্য কোনো আপস্ট্রিমে রিকোয়েস্টটি পুনরায় পাঠানোর (retry) ক্ষমতা হারায়, কারণ বডিটি ইতিমধ্যে চলে গেছে।
client_body_timeout, যা ডিফল্টভাবে 60 সেকেন্ড, তা পুরো আপলোডের সময়ের পরিবর্তে বডির দুটি ধারাবাহিক রিডের মধ্যবর্তী সময়ে কার্যকর হয়। ধীরগতির কিন্তু নিরবচ্ছিন্ন আপলোড এতে টিকে যায়। তবে আপলোড আটকে গেলে তা ড্রপ করা হয়।
রেসপন্স বাফারিং এবং যে সেটিংসটি লাইভ আউটপুট বাধাগ্রস্ত করে
proxy_buffering ডিফল্টভাবে চালু থাকে এবং সাধারণত এটিই প্রত্যাশিত। Nginx আপনার অ্যাপ থেকে রেসপন্সটি অ্যাপের লেখার গতি অনুযায়ী দ্রুত পড়ে নেয়, তা জমা রাখে এবং ধীরগতির ক্লায়েন্টের কাছে তার নিজস্ব গতিতে পাঠায়। এতে অ্যাপ ওয়ার্কার পুরো ডাউনলোড শেষ হওয়া পর্যন্ত ব্যস্ত না থেকে দ্রুত কাজ শেষ করতে পারে।
এটি স্ট্রিমিং রেসপন্সকে বাধাগ্রস্ত করে। Server-sent events এবং লাইভ লগ আউটপুট বাফার পূর্ণ না হওয়া পর্যন্ত ব্যবহারকারীকে কিছুই দেখায় না। শুধুমাত্র সেই লোকেশনে বাফারিং বন্ধ করে দিন।
proxy_buffering off;যদি অ্যাপটি আপনার নিয়ন্ত্রণে থাকে, তবে সবচেয়ে ভালো উপায় হলো শুধুমাত্র স্ট্রিমিং রেসপন্সের ক্ষেত্রে X-Accel-Buffering: no হেডারটি পাঠানো। Nginx প্রতিটি রেসপন্সের জন্য সেই হেডারটি পড়ে এবং শুধুমাত্র সেই নির্দিষ্ট রেসপন্সের জন্য বাফারিং বন্ধ করে দেয়, ফলে সাধারণ পেজগুলো বাফারিংয়ের সুবিধা পেতে থাকে।
যখন এরর লগে upstream sent too big header while reading response header from upstream দেখা যায়, তার মানে হলো রেসপন্স হেডারগুলো একটি বাফারের মধ্যে ধরেনি। proxy_buffer_size ডিফল্টভাবে একটি মেমোরি পেজ হিসেবে থাকে, যা প্ল্যাটফর্মভেদে 4 বা 8 কিলোবাইট হতে পারে। দীর্ঘ কুকি বা বড় অথেন্টিকেশন হেডার থাকলে তা বাফার ওভারফ্লো করে। সেক্ষেত্রে উভয় মানই বৃদ্ধি করুন।
proxy_buffer_size 16k;
proxy_buffers 8 16k;এই কনফিগারেশনে TLS কোথায় থাকবে?
Nginx-এ, উপরের সবকিছুর সামনে। TLS (transport layer security) প্রক্সিতেই শেষ (terminate) হয় এবং Nginx থেকে অ্যাপ পর্যন্ত সংযোগটি loopback অ্যাড্রেসের মাধ্যমে plain HTTP হিসেবে থাকে, যেখানে নেটওয়ার্কের অন্য কেউ এটি পড়তে পারে না। অ্যাপটি চারটি হেডারের মধ্যে চতুর্থটি, অর্থাৎ X-Forwarded-Proto থেকে জানতে পারে যে ভিজিটর HTTPS ব্যবহার করেছে।
সার্টিফিকেট পাথ নিজে হাতে লিখবেন না। DNS রেকর্ডটি সার্ভারের দিকে নির্দেশ করুন, ফায়ারওয়াল খুলুন এবং Certbot-কে এই একই সার্ভার ব্লকটি এডিট করতে দিন: এটি ssl_certificate পাথসহ listen 443 ssl লাইনটি যোগ করে, সেই সাথে পোর্ট 80 থেকে একটি রিডাইরেক্টও তৈরি করে। Certbot ব্যবহার করে Nginx-এর জন্য Let's Encrypt সার্টিফিকেট ইস্যু করা অংশে ইস্যুয়েন্স এবং রিনিউয়াল টাইমার সম্পর্কে বিস্তারিত আলোচনা করা হয়েছে।
sudo ufw allow 'Nginx Full'
sudo ufw statusNginx Full হলো একটি অ্যাপ্লিকেশন প্রোফাইল যা Nginx প্যাকেজ ইনস্টল করে এবং এটি একসাথে পোর্ট 80 ও পোর্ট 443 খুলে দেয়। প্রতিটি ভিজিটরকে HTTPS-এ রিডাইরেক্ট করার পরেও HTTP-01 রিনিউয়াল চ্যালেঞ্জের জন্য পোর্ট 80 খোলা রাখা আবশ্যক।
কনফিগারেশন পরীক্ষা করুন, তারপর রিলোড দিন
sudo nginx -t
sudo systemctl reload nginxnginx -t প্রতিটি অন্তর্ভুক্ত ফাইল পার্স করে এবং পরীক্ষা সফল হলে তা জানায়, অথবা যে ফাইল ও লাইনে ত্রুটি পাওয়া গেছে তা প্রদর্শন করে। রিলোড দেওয়ার আগে এই আউটপুটটি পড়ুন। ভুল কনফিগারেশন নিয়ে রিলোড দিলে তা কার্যকর হয় না: Nginx আগের কনফিগারেশন অনুযায়ীই চলতে থাকে, ফলে আপনার পরিবর্তনটি কোনো প্রভাব না ফেলেই সাইট সচল রাখে। systemctl restart ভিন্নভাবে এবং আরও খারাপ আচরণ করে, কারণ রিস্টার্ট দেওয়ার সময় এটি চলমান সার্ভারটিকে প্রথমে বন্ধ করে দেয়; ফলে কনফিগারেশনে ভুল থাকলে Nginx আর চালু হয় না। সাধারণত রিলোড দিন এবং শুধুমাত্র যেসব পরিবর্তনের জন্য প্রয়োজন, কেবল তখনই রিস্টার্ট ব্যবহার করুন।
sudo tail -f /var/log/nginx/error.log
sudo ss -lntp | grep -E ':(80|443|3000)'ss লাইনটি দেখায় কোন প্রসেস কোন পোর্ট দখল করে আছে, যাতে আপনি নিশ্চিত হতে পারেন যে অ্যাপটি সত্যিই সেই পোর্টে লিসেন করছে যেখানে proxy_pass নির্দেশ করছে।
যেসব ব্যর্থতার সম্মুখীন আপনি হবেন
502 Bad Gateway, সাথে এরর লগে connect() failed (111: Connection refused) while connecting to upstream। proxy_pass-এ উল্লেখিত ঠিকানায় কোনো কিছু লিসেন করছে না। অ্যাপটি বন্ধ আছে, অথবা অন্য কোনো পোর্টে বাইন্ড করা আছে, অথবা এমন কোনো কন্টেইনার-ইন্টারনাল ঠিকানায় বাইন্ড করা আছে যেখানে হোস্ট পৌঁছাতে পারছে না।
no live upstreams while connecting to upstream সহ 502 এরর। upstream ব্লকের প্রতিটি সার্ভারকে বর্তমানে max_fails দ্বারা ফেইলড হিসেবে চিহ্নিত করা হয়েছে। ব্যাকএন্ডগুলো মেরামত করুন। fail_timeout এর মেয়াদ শেষ হলে Nginx সেগুলোকে পুনরায় চেষ্টা করবে।
504 Gateway Time-out, সাথে upstream timed out (110: Connection timed out) while reading response header from upstream। ব্যাকএন্ড সংযোগটি গ্রহণ করেছে কিন্তু proxy_read_timeout সেকেন্ড পর্যন্ত কোনো কিছু পাঠায়নি। সত্যিকারের ধীরগতির রিপোর্টের জন্য টাইমআউট বাড়ানো সঠিক, কিন্তু আটকে থাকা অ্যাপের জন্য এটি ভুল সমাধান।
অ্যাপ থেকে প্রতিটি পাথ 404 রিটার্ন করছে। ট্রেইলিং স্ল্যাশ রুল পাথটিকে রিরাইট করেছে। অ্যাপ যে পাথ লগ করছে তার সাথে আপনার অনুরোধ করা পাথটি তুলনা করে দেখুন।
ভিন্ন একটি সাইট উত্তর দিচ্ছে। server_name এর সাথে Host হেডার মিলছে না, তাই অনুরোধটি default_server ব্লকে চলে গেছে।
পেজ লোড হয়, কিন্তু প্রায় এক মিনিট পর ইন্টারফেস ফ্রিজ হয়ে যায়। এটি WebSocket সংক্রান্ত সমস্যা: Upgrade হ্যান্ডলিং অনুপস্থিত, অথবা proxy_read_timeout এখনো 60 সেকেন্ডে সেট করা আছে।
FAQ
কেন proxy_pass যোগ করার পর nginx 502 Bad Gateway দেখায়?
nginx proxy_pass-এ উল্লেখিত ঠিকানায় সংযোগ স্থাপন করতে পারছে না। /var/log/nginx/error.log-এ থাকা এরর লগে কারণটি দেখা যাবে: connect() failed (111: Connection refused) while connecting to upstream মানে সেখানে কোনো সার্ভিস লিসেন করছে না, এবং no live upstreams মানে upstream ব্লকের প্রতিটি সার্ভারকে ফেইলড হিসেবে চিহ্নিত করা হয়েছে। কোন প্রসেস পোর্টটি ব্যবহার করছে এবং সেটি কোন ঠিকানায় বাইন্ড করা আছে তা দেখতে sudo ss -lntp | grep 3000 কমান্ডটি চালান। কোনো অ্যাপ যদি কন্টেইনারের অভ্যন্তরীণ ঠিকানায় বা আপনার লেখা পোর্ট ছাড়া অন্য কোনো পোর্টে বাইন্ড করা থাকে, তবে প্রতিবারই এই এররটি আসবে।
কেন nginx-এর পেছনে থাকা আমার অ্যাপ প্রায় এক মিনিট পর ডিসকানেক্ট হয়ে যায়?
সংযোগটি একটি WebSocket এবং proxy_read_timeout এখনো তার ডিফল্ট 60 সেকেন্ডে সেট করা আছে, যা ব্যাকএন্ড থেকে দুটি রিডের মধ্যবর্তী সময়ের ব্যবধান পরিমাপ করে। একটি নিষ্ক্রিয় সকেট nginx দ্বারা বন্ধ করে দেওয়া হয় এবং ব্রাউজার কনসোলে ক্লোজ কোড 1006 দেখা যায়। proxy_http_version 1.1 সেট করুন, $http_upgrade-এ map ব্যবহার করে Upgrade এবং Connection পাস করুন, এবং proxy_read_timeout-কে 3600s-এর মতো কোনো মানে উন্নীত করুন। Upgrade হেডার ছাড়া আপগ্রেড প্রক্রিয়াটি কখনোই সম্পন্ন হয় না, ফলে অ্যাপটি পোলিং মোডে ফিরে যায় অথবা কোনো লাইভ আপডেট দেখায় না।
proxy_pass-এ ট্রেইলিং স্ল্যাশ (trailing slash) কি গুরুত্বপূর্ণ?
হ্যাঁ, এটি আপনার ব্যাকএন্ডে প্রাপ্ত পাথ পরিবর্তন করে দেয়। location /app/ এবং proxy_pass http://127.0.0.1:3000/ এর ক্ষেত্রে, /app/status-এর জন্য একটি অনুরোধ ব্যাকএন্ডে /status হিসেবে পৌঁছায়, কারণ হোস্ট এবং পোর্টের পরের যেকোনো URI ম্যাচ করা লোকেশন প্রিফিক্সকে প্রতিস্থাপন করে। সেই শেষ স্ল্যাশটি বাদ দিলে একই অনুরোধ /app/status হিসেবে পৌঁছাবে। প্রিফিক্স সরিয়ে ফেললে প্রায়শই অ্যাপের নিজস্ব অ্যাসেট লিঙ্কগুলো ভেঙে যায়, কারণ সেগুলো অ্যাবসোলিউট থাকে এবং সাইটের রুটে 404 এরর দেখায়। তাই বেস-পাথ সেটিং থাকা অ্যাপের ক্ষেত্রে এমন ফরম্যাট ব্যবহার করা ভালো যা পাথটিকে অপরিবর্তিত রাখে।
কেন আমার অ্যাপ্লিকেশন প্রতিটি ভিজিটরের আইপি অ্যাড্রেস হিসেবে 127.0.0.1 লগ করে?
কারণ অ্যাপটি যে সংযোগ পায় তা আসলে লুপব্যাক অ্যাড্রেস থেকে nginx-এর মাধ্যমে আসে। ভিজিটরের আসল অ্যাড্রেস অ্যাপের কাছে কেবল আপনার সেট করা হেডারের মাধ্যমেই পৌঁছায়: একটি ভ্যালুর জন্য proxy_set_header X-Real-IP $remote_addr;, এবং অ্যাপেন্ড করা চেইনের জন্য proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;। এরপর অ্যাপটিকে এমনভাবে কনফিগার করতে হবে যাতে সে এই হেডারগুলোকে বিশ্বাস করে। মনে রাখবেন, একজন ক্লায়েন্ট নিজেই নিজের X-Forwarded-For পাঠাতে পারে, তাই যখন nginx এজ সার্ভার হিসেবে কাজ করে, তখন অ্যাপেন্ড না করে $remote_addr দিয়ে সেটি ওভাররাইট করুন।
nginx এবং আমার অ্যাপের মধ্যকার সংযোগে কি TLS প্রয়োজন?
যখন অ্যাপটি একই সার্ভারে চলে এবং 127.0.0.1-এ বাইন্ড করা থাকে, তখন TLS-এর প্রয়োজন নেই, কারণ সেই ট্রাফিক মেশিন থেকে বাইরে যায় না। nginx-এ TLS টার্মিনেট করুন, লুপব্যাক-এ proxy_pass-কে প্লেইন HTTP-তে রাখুন এবং X-Forwarded-Proto $scheme পাঠান যাতে অ্যাপ বুঝতে পারে ভিজিটর HTTPS ব্যবহার করেছে। যদি ব্যাকএন্ড অন্য কোনো হোস্টের নেটওয়ার্কে থাকে যা আপনার নিয়ন্ত্রণে নেই, তবে সেই সংযোগের জন্য নিজস্ব সুরক্ষার প্রয়োজন হবে, হয় ব্যাকএন্ডে HTTPS ব্যবহার করুন অথবা দুটি মেশিনের মধ্যে একটি প্রাইভেট টানেল তৈরি করুন।