Streamlit, Nginx and IPv6: How a Simple localhost Caused Intermittent Production Failures

Streamlit, Nginx and IPv6: How a Simple localhost Caused Intermittent Production Failures

Published on 06/26/2026 · Network

Learn how a simple localhost configuration between Nginx and Streamlit caused intermittent production failures. Follow the complete troubleshooting process, log analysis, root cause investigation, and the permanent fix involving IPv4, IPv6, and reverse proxy configuration.

Streamlit, Nginx and IPv6: How a Simple localhost Caused Intermittent Production Failures

Summary

  1. The production issue
  2. Why the failures appeared random
  3. Initial hypotheses
  4. Reading the Nginx logs
  5. The breakthrough: http://[::1]:8501
  6. Understanding how localhost really works
  7. Why Streamlit refused the connection
  8. Fixing the Streamlit service
  9. Fixing the Nginx reverse proxy
  10. Validating the solution
  11. Lessons learned

Streamlit, Nginx and IPv6: How a Simple localhost Caused Intermittent Production Failures

Intermittent production issues are among the most difficult problems to troubleshoot. When an application fails consistently, identifying the cause is usually straightforward. When it works most of the time—but randomly fails for some users—the investigation becomes far more challenging.

This case involved a production deployment of a Streamlit application running behind Nginx as a reverse proxy. At first glance, the symptoms pointed toward authentication problems or application instability. In reality, the root cause turned out to be something much smaller: the use of localhost.

What looked like an innocent configuration choice created an inconsistency between IPv4 and IPv6 that generated connection failures over several weeks.

This article walks through the entire investigation, following the exact reasoning process that led from confusing symptoms to the definitive solution.


The Initial Symptoms

The application was available online, but users occasionally experienced problems such as:

  • Login failures
  • Pages loading only partially
  • Missing JavaScript resources
  • Static assets failing to load
  • Random interface failures
  • Inconsistent user experience

The most confusing aspect was that the application never appeared completely offline.

Sometimes everything worked perfectly.

Sometimes it didn't.

That inconsistency made the issue particularly difficult to diagnose.

Meanwhile, the Nginx error log repeatedly showed the following message:

connect() failed (111: Connection refused)

A "Connection refused" error means something very specific:

The reverse proxy successfully reached the operating system, but nothing was listening on the destination address and port.

The question immediately became:

Which address was Nginx actually trying to reach?


The First Hypotheses

Like many production investigations, the first assumptions seemed reasonable.

Possible causes included:

  • Authentication failures
  • Auth0 integration issues
  • Streamlit restarting unexpectedly
  • Resource exhaustion
  • Server instability
  • Temporary network failures

Each possibility explained part of the symptoms.

None explained all of them.

If Auth0 were responsible, errors would mostly appear during authentication.

If Streamlit were crashing, the entire application would become unavailable.

If the server were unstable, failures would correlate with high load.

None of those patterns appeared in the logs.

That meant it was time to stop guessing and start collecting evidence.


Looking at the Nginx Logs

The investigation shifted to the Nginx error logs.

Instead of focusing on isolated errors, the entire history was analyzed.

The first occurrence appeared on:

2026/06/12

The latest occurrence was recorded on:

2026/06/26

This immediately changed the perspective.

The problem had not started recently.

It had been silently affecting production for approximately two weeks.

Daily occurrences revealed that the issue never completely disappeared.

It kept happening every single day.

After filtering out administrative access, the remaining errors clearly originated from external users.

That confirmed something important:

This was not a local testing issue.

Real users were being affected.


The Breakthrough Discovery

Eventually, one detail inside every error message stood out:

upstream: "http://[::1]:8501"

This single line explained almost everything.

Notice what Nginx was doing.

It wasn't connecting to:

127.0.0.1:8501

Instead, it was attempting to connect to:

[::1]:8501

That distinction is critical.

127.0.0.1 is the IPv4 loopback address.

::1 is the IPv6 loopback address.

At that moment, the investigation changed direction completely.

The question was no longer:

"Is Streamlit crashing?"

Instead, it became:

"Is Streamlit actually listening on IPv6?"


Understanding Why localhost Isn't Always the Same Address

The Nginx configuration originally contained:

proxy_pass http://localhost:8501;

Most administrators naturally assume that localhost always means:

127.0.0.1

However, modern Linux systems usually define localhost twice inside /etc/hosts:

127.0.0.1 localhost
::1 localhost

That means localhost does not represent a single address.

It represents two possible loopback addresses.

Depending on how name resolution works, an application may choose either:

127.0.0.1

or

::1

Nginx was clearly selecting IPv6 in many requests.

The logs proved it.


Why Streamlit Rejected the Connection

The Streamlit service was not listening on IPv6.

Instead, it was accepting connections only through IPv4.

The communication flow looked like this:

User
   ↓
Nginx
   ↓
localhost
   ↓
::1
   ↓
No Streamlit process
   ↓
Connection refused

The problem wasn't Nginx.

The problem wasn't Streamlit.

The problem wasn't Linux.

The real issue was the mismatch between the address selected by the reverse proxy and the address where the backend application was actually listening.

This is an important distinction.

The infrastructure itself was working correctly.

The configuration was inconsistent.


Measuring the Impact

The investigation found:

2319 HTTP requests

After removing administrative traffic:

1655 external requests

Among them:

452 connection errors

Approximately:

27%

It is important to interpret these numbers correctly.

They do not represent lost visitors.

A single Streamlit page generates multiple HTTP requests, including:

  • HTML
  • JavaScript
  • CSS
  • Static assets
  • Media
  • WebSocket connections
  • Internal Streamlit endpoints

Therefore, one failed user session may generate many error log entries.

Nevertheless, the continuous presence of errors over two weeks demonstrated a genuine production problem.


Fixing the Streamlit Service

Instead of allowing Streamlit to bind implicitly, the service was configured explicitly.

The original systemd service launched Streamlit without defining the listening address.

After the correction:

ExecStart=/home/streamlit/football.hacking/venv/bin/streamlit run /home/streamlit/football.hacking/app.py --server.address=127.0.0.1 --server.port=8501

Now Streamlit listens explicitly on:

127.0.0.1:8501

This provides two important advantages.

First, the backend becomes predictable.

Second, it improves security by preventing direct exposure on external interfaces.

Since Nginx is already responsible for handling incoming traffic, there is no reason for Streamlit itself to listen on every network interface.


Fixing the Nginx Reverse Proxy

The second change was equally important.

The original configuration:

proxy_pass http://localhost:8501;

was replaced with:

proxy_pass http://127.0.0.1:8501;

This removes every ambiguity.

There is no DNS lookup.

No hostname resolution.

No IPv4 versus IPv6 decision.

Both processes now communicate using exactly the same address.

The architecture became:

Internet
     ↓
Nginx
     ↓
127.0.0.1:8501
     ↓
Streamlit

Simple.

Explicit.

Predictable.


Deploying the Fix Safely

Before changing production configuration, backups were created.

Nginx configuration backup:

sudo cp /etc/nginx/sites-available/streamlit \
/etc/nginx/sites-available/streamlit.bak.$(date +%F-%H%M)

Systemd unit backup:

sudo cp /etc/systemd/system/streamlit.service \
/etc/systemd/system/streamlit.service.bak.$(date +%F-%H%M)

After editing the service:

sudo systemctl daemon-reload

Restart Streamlit:

sudo systemctl restart streamlit

Reload Nginx:

sudo systemctl reload nginx

This sequence minimizes deployment risk while ensuring every change is properly applied.


Validating the Fix

The first validation step was verifying where Streamlit was listening.

sudo ss -ltnp | grep 8501

Expected result:

127.0.0.1:8501

This command immediately confirms whether the backend is listening on the intended interface.

Next, the logs were monitored:

sudo grep '\[::1\]:8501' /var/log/nginx/error.log

or

sudo grep 'Connection refused' /var/log/nginx/error.log

The expected outcome is straightforward:

No new connections attempting to reach:

http://[::1]:8501

Once those entries disappear, the root cause has been eliminated.


The Logical Troubleshooting Process

The most valuable lesson from this investigation is not the final configuration.

It is the reasoning process.

The diagnosis followed this sequence:

  1. Users reported intermittent failures.
  2. Nginx consistently logged "Connection refused."
  3. A refused connection means the destination accepted no connections.
  4. The destination address was identified.
  5. The address turned out to be IPv6 (::1).
  6. Streamlit was not listening on IPv6.
  7. Nginx was using localhost.
  8. localhost could resolve to IPv4 or IPv6.
  9. Both services were configured to use different assumptions.
  10. Making both configurations explicit solved the problem permanently.

Every troubleshooting session should follow this pattern.

Observe.

Collect evidence.

Verify assumptions.

Identify the real cause.

Only then apply the fix.


Lessons Learned

Several important engineering lessons emerged from this incident.

Do not rely on implicit behavior in production.

Whenever possible, use explicit IP addresses instead of hostnames for local service communication.

Reverse proxies should be deterministic.

If the backend listens on IPv4, configure the proxy to use IPv4.

Logs tell a story.

The most valuable clue was already present inside every Nginx error message.

The challenge wasn't finding information.

It was recognizing its significance.

Intermittent problems are rarely random.

Most "random" failures simply follow patterns that have not yet been identified.

Small configuration details can have large production impact.

One word—localhost—was enough to affect users for weeks.


Conclusion

This production incident demonstrates why systematic troubleshooting consistently outperforms trial-and-error debugging.

Although the visible symptom was an intermittent Streamlit failure, the underlying cause had nothing to do with application logic, authentication, or server performance.

Instead, the root cause was an ambiguous network configuration.

By replacing localhost with an explicit IPv4 address and configuring Streamlit to listen on exactly the same interface, the communication path became deterministic, predictable, and reliable.

Sometimes the smallest configuration choices have the largest operational consequences.

In production infrastructure, explicit configuration almost always wins over implicit behavior.

Frequently Asked Questions

Why did localhost cause problems?

Because localhost can resolve to either IPv4 (127.0.0.1) or IPv6 (::1). If the backend is not listening on both protocols, connection failures may occur.

Was this a Streamlit bug?

No. Streamlit was behaving correctly. The issue was an inconsistent network configuration between Streamlit and Nginx.

Why is 127.0.0.1 preferable in this case?

It removes hostname resolution entirely and guarantees that both applications communicate using the same network protocol.

Is IPv6 the problem?

Not at all. IPv6 works perfectly when every component is configured consistently. The issue was mixing IPv4-only services with hostname resolution that sometimes selected IPv6.

How can similar issues be prevented?

Use explicit addresses for backend services, ensure the reverse proxy matches the backend listening interface, and regularly monitor Nginx error logs for recurring connection failures.