SSD Nodes Learn
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-07-24

Docker UFW को bypass क्यों करता है

Docker iptables rules का उपयोग करके UFW को bypass कर देता है। इस समस्या के सटीक कारण और इसे ठीक करने के दो प्रभावी समाधान यहाँ विस्तार से जानें।

Docker, UFW को क्यों bypass करता है

Docker, UFW को bypass करता है क्योंकि published container ports कभी भी उन firewall rules से नहीं गुजरते जिन्हें UFW manage करता है। जब आप docker run -p 8080:80 चलाते हैं, तो Docker kernel के nat table की PREROUTING chain में एक DNAT (destination network address translation) rule लिख देता है। वह rule kernel द्वारा packet के गंतव्य (destination) का निर्णय लेने से पहले, प्रत्येक packet के destination को container के private address में बदल देता है। इसके बाद, rewritten packet को FORWARD chain के माध्यम से container में forward कर दिया जाता है, जिसे Docker control करता है। UFW के rules INPUT chain में होते हैं, और packet कभी उसमें प्रवेश नहीं करता है। इसलिए ufw status default deny दिखाता है, sudo ufw deny 8080 success report करता है, फिर भी port 8080 पूरे internet के लिए खुला रहता है।

यह Docker का bug नहीं है, और UFW भी खराब नहीं है। दोनों tools एक ही kernel firewall को program करते हैं। Docker के rules packet के path पर एक पहले के point पर काम करते हैं, इसलिए UFW से कभी पूछा ही नहीं जाता। यह guide इस bypass को demonstrate करती है, इसके mechanism को समझाती है, और फिर दो प्रभावी समाधान (fixes) बताती है: 127.0.0.1 पर ports publish करना, और DOCKER-USER chain में filtering करना। यदि आप UFW के लिए नए हैं, तो पहले the UFW firewall basics guide के साथ इसे setup करें, क्योंकि server पर अन्य सभी चीजों के लिए default-deny firewall ही सही आधार है।

अपने स्वयं के server पर bypass देखें

एक ऐसे VPS से शुरुआत करें जहाँ UFW active है और incoming traffic के लिए default deny policy लागू है। एक published port के साथ web container चलाएँ:

sudo ufw status verbose
docker run -d --name web -p 8080:80 nginx:1.29-alpine

ufw status verbose में Default: deny (incoming), allow (outgoing) दिखाई देता है और port 8080 के लिए कोई rule नहीं है। Firewall की अपनी report के अनुसार, port closed है। अब server के बजाय किसी दूसरी machine से test करें:

curl -I http://your-vps-ip:8080/
HTTP/1.1 200 OK

Container जवाब देता है। एक explicit deny rule जोड़ें और फिर से test करें:

sudo ufw deny 8080/tcp

Port अभी भी जवाब दे रहा है, क्योंकि deny rule एक ऐसे chain में है जहाँ packet कभी पहुँचता ही नहीं है। UFW fail नहीं हुआ। उससे कभी परामर्श (consult) ही नहीं किया गया। यही कारण है कि यह समस्या इतनी अच्छी तरह से छिपी रहती है: कहीं भी कोई error print नहीं होता, deployment सही काम करता है, और firewall status output बिल्कुल एक स्वस्थ locked-down server जैसा दिखता है।

Mechanism: PREROUTING, INPUT से पहले चलता है

Kernel आने वाले packet को एक निश्चित क्रम में process करता है, और पूरी समस्या इसी क्रम में है।

  1. सबसे पहले PREROUTING चलता है। यहाँ के rules packet के destination को rewrite कर सकते हैं, और Docker का published port वाला rule यही करता है।
  2. इसके बाद routing decision आता है। Host के लिए address किया गया packet INPUT chain में जाता है। किसी अन्य machine के लिए address किया गया packet FORWARD chain में जाता है।
  3. UFW के rules INPUT में होते हैं। Docker के rules FORWARD में होते हैं।

अभी शुरू किए गए container के लिए Docker का rule देखें:

sudo iptables -t nat -L DOCKER -n
Chain DOCKER (2 references)
target   prot opt source       destination
RETURN   0    --  0.0.0.0/0    0.0.0.0/0
DNAT     6    --  0.0.0.0/0    0.0.0.0/0    tcp dpt:8080 to:172.17.0.2:80

पूरी जानकारी DNAT line में है। Port 8080 के लिए आने वाले किसी भी packet का destination rewrite होकर 172.17.0.2:80 हो जाता है, जो Docker के private bridge network पर container का address है। Rewrite होने के बाद packet अब host के लिए address नहीं रहता, इसलिए routing decision इसे FORWARD path पर भेज देता है, जहाँ Docker ने पहले ही अपने networks में traffic accept करने के लिए rules जोड़ दिए हैं। आपका deny 8080/tcp rule INPUT में उस packet का इंतज़ार करता है जो कभी आता ही नहीं।

Ubuntu 24.04 पर iptables command, nftables के ऊपर एक front end है, लेकिन chain का क्रम और परिणाम समान हैं। UFW और Docker दोनों एक ही kernel packet pipeline में write करते हैं, और Docker का entry point पहले आता है।

रोज़ाना का समाधान: ports को 127.0.0.1 पर publish करें

ज़्यादातर containers को शुरुआत में ही public होने की ज़रूरत नहीं होती है। एक database, reverse proxy के पीछे लगा app server, admin panel, या metrics endpoint: इनमें से किसी को भी सीधे internet से response नहीं देना चाहिए। इन्हें loopback address पर publish करें:

docker run -d --name web -p 127.0.0.1:8080:80 nginx:1.29-alpine

या Compose file में:

services:
  web:
    image: nginx:1.29-alpine
    ports:
      - "127.0.0.1:8080:80"

यह इसलिए काम करता है क्योंकि Docker का DNAT rule अब केवल 127.0.0.1 के लिए addressed packets से match करता है। internet से आने वाला packet कभी भी वैध रूप से (legitimately) उस destination को carry नहीं कर सकता, इसलिए kernel किसी भी firewall rule के चलने से पहले ही उसे drop कर देता है। port host से reachable होता है, और इसके अलावा कहीं और से नहीं। binding को verify करें:

sudo ss -tlnp | grep 8080

output में आपको 127.0.0.1:8080 चाहिए, 0.0.0.0:8080 या [::]:8080 नहीं। फिर किसी दूसरी machine से confirm करें कि curl http://your-vps-ip:8080/ refused है।

जिन services को internet के सामने होना चाहिए, उनके लिए एक reverse proxy चलाएँ जो ports 80 और 443 को handle करे और hostname के आधार पर route करे, और इसके अलावा कुछ भी publish न करें। यही वह pattern है जिसे Traefik reverse proxy guide में समझाया गया है, और इसी तरह Nextcloud on a VPS जैसे self-hosted app अपने proxy के अलावा कहीं और से unreachable रहते हैं। ports: entries को कैसे declare किया जाता है, और Compose workflow का बाकी हिस्सा, the Docker Compose basics guide में दिया गया है।

जब हर internal container loopback पर होता है, तो UFW अपने सामान्य काम पर वापस आ जाता है: उन ports की सुरक्षा करना जिन्हें host खुद serve करता है। यहाँ rule set बनाएँ, फिर commands को क्रम में चलाएँ:

ToolUFW rule generator

Real filtering: the DOCKER-USER chain

कभी-कभी किसी container port को network पर published रखना आवश्यक होता है, लेकिन उसे प्रतिबंधित (restrict) करना पड़ता है। उदाहरण के लिए, एक database replica port जिसे केवल एक विशिष्ट office address ही एक्सेस कर सके। इसके लिए Docker DOCKER-USER chain प्रदान करता है। Docker के अपने accept rules से पहले, हर packet जो किसी container की ओर जाता है, वह DOCKER-USER से होकर गुजरता है। Docker इस chain में स्वयं कोई rules नहीं लिखता है। यह chain आपके उपयोग के लिए है, और Docker daemon restart होने पर भी इसके contents को नहीं बदलता है।

command से पहले एक सावधानी: जब तक packet DOCKER-USER तक पहुँचता है, तब तक DNAT rewrite पहले ही हो चुका होता है। Packet का destination port container port (हमारे उदाहरण में 80) होता है, न कि published port (8080)। इसलिए, --dport 8080 से match होने वाला rule किसी भी packet को match नहीं करेगा। सही तरीका वह port match करना है जिसे client ने मूल रूप से dial किया था, जिसे kernel का connection tracker याद रखता है:

sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROP

इसे इस प्रकार समझें: उन packets के लिए जो eth0 पर आए हैं और उस connection का हिस्सा हैं जिसका original destination port 8080 था, 10.0.0.10 के अलावा बाकी सभी packets को drop कर दें। --ctdir ORIGINAL match इस rule को केवल client-to-container direction तक सीमित करता है, जिससे reply packets गलती से block नहीं होते। eth0 को अपने public interface से बदलें; ip route | grep default उसका नाम बताता है। इसे पहले की तरह ही test करें: allowed address से curl सफल होता है, और किसी अन्य स्थान से connection time out हो जाता है।

iptables command से जो rules जोड़े जाते हैं, वे reboot के बाद हट जाते हैं। चूंकि UFW पहले से ही इस firewall को manage कर रहा है, इसलिए उन्हें सुरक्षित रखने का सही स्थान /etc/ufw/after.rules है। file के अंत में यह block जोड़ें:

*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROP
COMMIT

फिर sudo ufw reload चलाएँ। UFW हर reload और हर boot पर उस file को replay करता है, इसलिए आपका container filtering अब आपके firewall के बाकी हिस्सों की तरह ही एक ही स्थान पर रहता है, और यह reboot और Docker upgrade दोनों में सुरक्षित रहता है।

आपको Docker के iptables integration को disable क्यों नहीं करना चाहिए

इस समस्या के पुराने उत्तरों में /etc/docker/daemon.json में { "iptables": false } सेट करने का सुझाव दिया गया है। ऐसा न करें। Docker के firewall rules केवल ports publish करने तक सीमित नहीं हैं। Masquerade rule ही containers को host के address के माध्यम से outbound internet access प्रदान करता है। यदि integration बंद कर दिया गया, तो containers images pull नहीं कर पाएंगे, package mirrors तक नहीं पहुँच पाएंगे, या किसी भी external API (application programming interface) को call नहीं कर पाएंगे। DNAT rules ही -p को कार्य करने में सक्षम बनाते हैं, इसलिए published ports पूरी तरह से काम करना बंद कर देंगे। अलग-अलग Compose networks को अलग रखने वाले isolation rules भी खत्म हो जाएंगे। इस bypass को ठीक करने के चक्कर में आप container networking को ही खराब कर देंगे, और आपको उन सभी rules को खुद लिखना और maintain करना होगा। Docker का अपना documentation इस setting को उन लोगों के लिए बताता है जो ठीक यही करना चाहते हैं। DOCKER-USER chain इसीलिए मौजूद है ताकि किसी को भी इस switch की आवश्यकता न पड़े।

उसी समस्या का IPv6 पक्ष

सबसे पहले IPv6 पर published port की स्थिति जांचें:

sudo ss -tlnp | grep 8080

Docker Engine 27 से, Docker डिफ़ॉल्ट रूप से ip6tables को मैनेज करता है। IPv6 सक्षम Docker network पर, published port को IPv6 tables में भी समान DNAT ट्रीटमेंट मिलता है। इसलिए वहां भी वही bypass मौजूद है और वही समाधान लागू होता है: ip6tables में DOCKER-USER chain भी मौजूद है, इसलिए अपनी rule को sudo ip6tables -I DOCKER-USER ... के साथ mirror करें और server के public IPv6 address, जैसे कि curl -6 http://[2001:db8:2a::1]:8080/, पर curl का उपयोग करके बाहर से टेस्ट करें।

बिना IPv6 वाले network पर, IPv6 clients को docker-proxy द्वारा हैंडल किया जाता है। यह एक सामान्य user-space process है जो [::]:8080 पर listen करता है और traffic को IPv4 के माध्यम से container में forward करता है। Host process का traffic INPUT से होकर जाता है, इसलिए UFW उस path को filter कर सकता है, लेकिन यह केवल तभी संभव है जब UFW IPv6 को manage कर रहा हो। क्या वह कर रहा है, और VPS पर IPv6 gap खुलने के अन्य तरीके, UFW और IPv6 guide का विषय हैं।

Loopback पर publish करने से यह पूरा प्रश्न समाप्त हो जाता है: -p 127.0.0.1:8080:80 केवल IPv4 loopback को bind करता है, इसलिए कोई IPv6 listener नहीं होता है और किसी भी stack पर बाहर से पहुँचने के लिए कुछ भी उपलब्ध नहीं होता है।

काम करने वाला पैटर्न

  • हर internal port को 127.0.0.1 पर publish करें, ताकि वह कभी expose न हो सके।
  • Public side के लिए एक reverse proxy का उपयोग करें जो ports 80 और 443 को manage करता है।
  • Host के लिए UFW default deny रखें, और केवल SSH और proxy ports को allow करें।
  • DOCKER-USER में genuine public container ports को filter करें, जिन्हें original destination port के आधार पर match किया गया हो और /etc/ufw/after.rules में store किया गया हो।
  • Docker का iptables integration चालू रखें।

इसे एक बार setup करने के बाद, अनचाहे exposure का डर खत्म हो जाता है: ufw status host का विवरण देता है, और DOCKER-USER containers का विवरण देता है। कोई भी चीज़ गलती से publish नहीं होती है, और अगली बार जब आप docker run -p टाइप करेंगे, तो केवल वही expose होगा जो आप करना चाहते हैं।

FAQ

UFW द्वारा port block किए जाने पर भी मैं अपने Docker container तक कैसे पहुँच पा रहा हूँ?

ऐसा इसलिए होता है क्योंकि Docker, PREROUTING chain में DNAT rule का उपयोग करके port publish करता है। यह rule किसी भी filtering से पहले packet के destination को container के address में बदल देता है। इसके बाद packet FORWARD path से गुजरता है, जबकि UFW के rules INPUT chain में होते हैं। Packet कभी भी इस chain में नहीं पहुँचता। Firewall से कोई परामर्श नहीं लिया जाता, इसलिए published container ports पर इसके deny rules का कोई प्रभाव नहीं पड़ता।

मैं Docker के published ports को UFW द्वारा block कैसे करूँ?

UFW स्वयं ऐसा नहीं कर सकता, क्योंकि इसके rules गलत chain में होते हैं। या तो port को expose करना बंद करें, उसे 127.0.0.1:8080:80 के रूप में publish करें ताकि केवल host ही उस तक पहुँच सके, या फिर conntrack के माध्यम से original destination port से मेल खाने वाले iptables rule के साथ DOCKER-USER chain में filter करें। उस rule को /etc/ufw/after.rules में save करें ताकि वह reboots और ufw reload के बाद भी बना रहे।

क्या मुझे Docker के daemon.json में "iptables": false सेट करना चाहिए?

नहीं। यह setting Docker के सभी firewall और NAT rules को हटा देती है, जिससे bypass की तुलना में कहीं अधिक समस्याएँ आती हैं। Containers का outbound internet access खत्म हो जाता है क्योंकि masquerade rule हट जाता है, और published ports काम करना बंद कर देते हैं क्योंकि DNAT rules हट जाते हैं। इसके बजाय loopback publishing और DOCKER-USER chain का उपयोग करें; ये container networking को खराब किए बिना exposure को ठीक करते हैं।

क्या Docker IPv6 पर भी UFW को bypass करता है?

Docker Engine 27 और उसके बाद के versions में, ip6tables management default रूप से on होता है। इसलिए IPv6-enabled Docker network पर publish किया गया port, IPv4 की तरह ही UFW को bypass कर देता है, और इसे ip6tables के साथ DOCKER-USER rule को mirror करने की आवश्यकता होती है। जिन networks में IPv6 नहीं है, वहाँ docker-proxy process [::] पर listen करता है और वह traffic INPUT से गुजरता है, जहाँ UFW उसे filter कर सकता है यदि UFW, IPv6 को manage करता है। 127.0.0.1 पर publish करने से दोनों स्थितियों से बचा जा सकता है, क्योंकि IPv6 पर कुछ भी listen नहीं करता है।