<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: niyazi</title>
    <description>The latest articles on DEV Community by niyazi (@cicikus_niyazi).</description>
    <link>https://dev.to/cicikus_niyazi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4067745%2Fdd854ce8-a741-4c33-998e-ad29efec85f8.jpg</url>
      <title>DEV Community: niyazi</title>
      <link>https://dev.to/cicikus_niyazi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cicikus_niyazi"/>
    <language>en</language>
    <item>
      <title>The Same Setting, Three Different Answers: Why 0.0.0.0 Isn't Always What You Want</title>
      <dc:creator>niyazi</dc:creator>
      <pubDate>Fri, 07 Aug 2026 15:19:06 +0000</pubDate>
      <link>https://dev.to/cicikus_niyazi/the-same-setting-three-different-answers-why-0000-isnt-always-what-you-want-58h5</link>
      <guid>https://dev.to/cicikus_niyazi/the-same-setting-three-different-answers-why-0000-isnt-always-what-you-want-58h5</guid>
      <description>&lt;p&gt;There is a line in almost every Python web tutorial that nobody explains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copied it for weeks without thinking about it. Then I deployed the same application three times — to a local VM, to a production server, and into a container — and the correct value was different every time.&lt;/p&gt;

&lt;p&gt;Twice it was &lt;code&gt;0.0.0.0&lt;/code&gt;. Once, in the place that mattered most, it was not.&lt;/p&gt;

&lt;p&gt;That gap is worth writing about, because the setting itself is trivial and the reasoning behind it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Flag Actually Controls
&lt;/h2&gt;

&lt;p&gt;A server process doesn't "open a port." It creates a socket and binds it to an address. The bind address answers one question: &lt;strong&gt;which network interfaces should this socket accept connections from?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A machine has more than one interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lo&lt;/code&gt; (loopback)&lt;/strong&gt; — reachable only from inside the machine (&lt;code&gt;127.0.0.1&lt;/code&gt;). Packets addressed there never reach a physical network card; the kernel loops them straight back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;0.0.0.0&lt;/code&gt;&lt;/strong&gt; — a wildcard meaning &lt;em&gt;every interface this machine has&lt;/em&gt;, including ones added later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the flag isn't about security or convenience. It's about reachability — and reachability depends entirely on what sits in front of the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: The Local VM — &lt;code&gt;0.0.0.0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I was running the service inside a Multipass VM and wanted to hit it from the browser on my laptop.&lt;/p&gt;

&lt;p&gt;The laptop is outside the VM, so binding to loopback would have made the service invisible to it. &lt;code&gt;curl&lt;/code&gt; inside the VM would work; the browser outside would get connection refused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision:&lt;/strong&gt; wildcard bind. Nothing sits in front of the process, and nothing needs protecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: Production — &lt;code&gt;127.0.0.1&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here I copied the same line at first, and it was wrong.&lt;/p&gt;

&lt;p&gt;The production box has a public IP. Binding to &lt;code&gt;0.0.0.0&lt;/code&gt; there means the application is directly exposed to the internet: no TLS, no rate limiting, no authentication. Within hours of provisioning that server, its SSH logs showed hundreds of automated login attempts against usernames like &lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;oracle&lt;/code&gt;. The same scanners try HTTP ports.&lt;/p&gt;

&lt;p&gt;So the application binds to loopback, and Caddy — the reverse proxy — binds ports 80 and 443. Requests arrive at Caddy, get their TLS terminated, and are forwarded to &lt;code&gt;127.0.0.1:8000&lt;/code&gt;. That forwarded connection never leaves the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Bind Address Beats a Firewall Rule
&lt;/h2&gt;

&lt;p&gt;This is stronger than a firewall rule. I could have bound to &lt;code&gt;0.0.0.0&lt;/code&gt; and blocked port 8000 in &lt;code&gt;ufw&lt;/code&gt;. Same outcome, on paper.&lt;/p&gt;

&lt;p&gt;But a firewall rule is a second system that has to be correct, stay correct, and survive every future change someone makes to it. A loopback bind isn't a rule &lt;em&gt;about&lt;/em&gt; the socket — it's a property &lt;em&gt;of&lt;/em&gt; the socket. There's no configuration to get wrong later.&lt;/p&gt;

&lt;p&gt;Port 8000 isn't in my firewall rules at all. It doesn't need to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 3: Inside a Container — &lt;code&gt;0.0.0.0&lt;/code&gt; Again
&lt;/h2&gt;

&lt;p&gt;And here it flips back, for a reason that has nothing to do with the previous two.&lt;/p&gt;

&lt;p&gt;A container gets its own network namespace. It has its own &lt;code&gt;lo&lt;/code&gt;, its own interfaces, and its own view of the network — and its &lt;code&gt;127.0.0.1&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; the host's &lt;code&gt;127.0.0.1&lt;/code&gt;. They are different loopbacks in different namespaces.&lt;/p&gt;

&lt;p&gt;Bind to &lt;code&gt;127.0.0.1&lt;/code&gt; inside a container and the service becomes unreachable from the host entirely. Not "exposed but firewalled" — unreachable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inside the container: bind to wildcard&lt;/span&gt;
uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000

&lt;span class="c"&gt;# On the host: explicitly control exposure&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8000:8000 myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So inside the container you bind to &lt;code&gt;0.0.0.0&lt;/code&gt;, which is a much smaller claim than it sounds — it means every interface &lt;em&gt;the container&lt;/em&gt; has. What actually gets exposed to the outside world is decided separately, by the runtime port mapping. The isolation decision moved to the container boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model: Ask the Question, Don't Memorize the Value
&lt;/h2&gt;

&lt;p&gt;For a while I wanted a rule. &lt;em&gt;Always use loopback in production&lt;/em&gt; felt like the lesson.&lt;/p&gt;

&lt;p&gt;But that rule breaks in containers, breaks behind a load balancer on a private network, and breaks again in a Kubernetes pod where the network namespace is shared across containers.&lt;/p&gt;

&lt;p&gt;The lesson isn't a memorized value. It's a question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What sits between this socket and the untrusted network, and is it doing its job?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If something trustworthy is in front — a reverse proxy on the same host, or a container boundary with explicit port mapping — bind narrowly and let that layer decide exposure.&lt;/p&gt;

&lt;p&gt;If nothing is in front, either bind narrowly or accept that you are publishing the service to the world.&lt;/p&gt;

&lt;p&gt;A memorized value gives you the right answer in one context. The question gives you the right answer in all of them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Running example: a FastAPI service on a €6/month VPS, behind Caddy, supervised by systemd. Code and architecture notes: &lt;a href="https://github.com/sss1sssq/fastapi-vps-deploy" rel="noopener noreferrer"&gt;fastapi-vps-deploy on GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>docker</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
