Deploying a containerized enterprise .NET application and a companion database behind a managed Linux web host introduces multiple abstraction layers. A breakdown in any single tier, from background thread initialization to firewall NAT rules, can prevent an application from serving traffic.

This technical guide outlines five core failure points that can occur when hosting a .NET API and SQL Server container behind a reverse proxy. Understanding how these layers interact can make deployment troubleshooting much easier.

1. Startup Thread Hijack and Kestrel Port Binding Failure

Modern .NET applications can use hosted services such as BackgroundService for background processing. Depending on the .NET version and how the hosted service is implemented, synchronous work performed during startup can delay application initialization.

A common problem occurs when a background worker encounters a failing external dependency and immediately enters a retry loop without yielding or applying a delay. This can consume resources and interfere with application startup.

For example, a worker that continuously retries an unavailable service without any delay can create unnecessary CPU and resource usage.

The Fix

The background operation should yield control and use an appropriate retry strategy with exponential backoff.

For example:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    await Task.Yield();

    while (!stoppingToken.IsCancellationRequested)
    {
        try
        {
            await ProcessWorkAsync(stoppingToken);
        }
        catch (Exception ex)
        {
            // Log the exception
        }

        await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
    }
}

For more sophisticated retry scenarios, resilience libraries such as Polly can be used to implement retry policies and exponential backoff.

The important point is that background processing should not continuously perform blocking or tight-loop work during application initialization.

2. Domain-to-Port Mapping on Web Hosting Panels

A containerized application may work correctly when accessed directly through a published port such as:

http://server-ip:8081

However, accessing the application through the root domain may display a generic hosting-panel test page instead.

This can happen when a managed Linux hosting panel generates or regenerates its web-server configuration and overwrites manually modified configuration files.

The Fix

Instead of directly modifying generated configuration files, use the hosting platform's supported userdata or custom-configuration include mechanism when available.

The reverse proxy can then forward requests from the domain to the application running on its published port.

For Apache, a configuration can look like this:

ProxyPass / http://192.168.1.50:8081/
ProxyPassReverse / http://192.168.1.50:8081/

This allows the public domain to act as the entry point while the .NET application continues running on its internal application port.

The exact configuration mechanism depends on the hosting panel and web server being used.

3. Loopback Isolation and Connection Resets

Another common reverse-proxy problem occurs when the web server returns:

502 Bad Gateway

The underlying logs may contain errors such as:

Connection reset by peer

This indicates that the reverse proxy was unable to establish or maintain a valid connection with the upstream application.

Container networking, host networking, firewall rules, and loopback behavior can all contribute to this problem.

The Fix

If routing through localhost does not work correctly in the deployment environment, configure the reverse proxy to use an appropriate reachable host interface, Docker bridge gateway, or published host address.

For example:

ProxyPass / http://192.168.1.50:8081/
ProxyPassReverse / http://192.168.1.50:8081/

The correct address should be verified against the actual network configuration rather than copied blindly.

Useful checks include:

docker ps

and:

curl http://192.168.1.50:8081

If the curl request succeeds from the host but the domain still returns 502, the problem is likely within the reverse-proxy configuration rather than the application itself.

4. The Containerized Database Connection Trap

One of the most common mistakes in a multi-container deployment is using localhost in the application's database connection string.

Consider a .NET application container and a SQL Server container:

.NET API Container
        |
        |
SQL Server Container

Inside the .NET API container, the following connection string does not normally point to the SQL Server container:

Server=localhost

Instead, localhost refers to the network namespace of the API container itself.

As a result, Entity Framework Core startup migrations or Identity initialization can fail because the application cannot find SQL Server.

This can cause the application container to repeatedly restart if database initialization is performed during startup.

The Fix

Place both containers on the same user-defined Docker bridge network.

For example:

docker network create enterprise-net

Start the SQL Server container on that network:

docker run \
  --name sql-container \
  --network enterprise-net \
  ...

Then start the .NET application on the same network:

docker run \
  --name api-container \
  --network enterprise-net \
  ...

Docker's internal DNS allows containers on the same user-defined network to communicate using container names.

The connection string can therefore reference the SQL Server container:

Server=sql-container;Database=EnterpriseDb;User Id=sa;Password=<password>;TrustServerCertificate=True;

The key difference is:

Server=localhost

versus:

Server=sql-container

The second value tells the application to connect to the SQL Server container through Docker's internal network.

5. Firewall and iptables Issues

Remote database connectivity can introduce another layer of troubleshooting.

For example, an administrator using SQL Server Management Studio (SSMS) may attempt to connect to:

server-ip:1433

If the connection times out, the issue may involve the SQL Server listener, Docker port publishing, the host firewall, cloud/network security rules, or iptables/NAT configuration.

Docker uses networking and NAT rules to route published container ports to the appropriate containers. Changes to host-level firewall configuration can therefore affect container connectivity.

The Fix

First, verify that SQL Server is actually publishing port 1433.

For example:

docker ps

Look for a mapping similar to:

0.0.0.0:1433->1433/tcp

Next, verify that the host firewall allows the required traffic.

After making firewall changes, Docker networking may need to be reinitialized depending on the firewall implementation and how its rules interact with Docker.

For example:

sudo systemctl restart docker

This can cause Docker to recreate its networking rules.

However, firewall changes should be made carefully in production environments. Restarting Docker can interrupt running containers, and exposing SQL Server directly to the public internet is generally not recommended unless there is a specific security requirement and appropriate access controls are in place.

Where possible, restrict database access to trusted IP addresses, private networks, VPNs, or other controlled network paths instead of exposing port 1433 broadly.

Deployment Troubleshooting Workflow

When a containerized .NET application is not reachable through its domain, troubleshooting each layer independently is usually more effective than changing multiple configurations at once.

A practical sequence is:

Step 1: Verify the Application Container

Check whether the application is running:

docker ps

Then inspect its logs:

docker logs api-container

Look for startup exceptions, database connection failures, migration errors, and Kestrel-related messages.

Step 2: Test the Application Port Directly

From the server, test the published application port:

curl http://127.0.0.1:8081

If this fails, investigate the container or its port mapping before troubleshooting the reverse proxy.

Step 3: Verify Container Networking

Check the Docker networks:

docker network ls

Then inspect the relevant network:

docker network inspect enterprise-net

Confirm that both the API and SQL Server containers are attached to the expected network.

Step 4: Test Database Connectivity

From the API container, verify that the database hostname can be resolved and reached.

For example:

docker exec -it api-container /bin/bash

Then test connectivity using the tools available in the container.

The important point is to verify that sql-container resolves to the SQL Server container rather than using localhost.

Step 5: Test the Reverse Proxy

Once the application responds directly on its published port, verify the reverse-proxy configuration.

A 502 Bad Gateway at this stage generally means the proxy cannot successfully communicate with its configured upstream.

Step 6: Check Firewall and NAT Rules

If direct local access works but external access fails, inspect firewall rules, published ports, cloud security groups, and Docker's networking configuration.

This separates application-level problems from host-level networking problems.

Common Failure Patterns

Symptom

Likely Area

What to Check

Container repeatedly restarts

Application startup

Logs, migrations, external dependencies

API works on :8081 but not domain

Reverse proxy

ProxyPass, virtual host, hosting-panel configuration

502 Bad Gateway

Reverse proxy/network

Upstream address, port, container reachability

Database connection refused

Docker networking

Shared network and connection string

Database hostname not found

Container DNS

Container network and service/container name

SSMS connection timeout

Firewall/network

Port 1433, firewall, NAT, network security rules

Application fails during Identity initialization

Database connectivity

SQL Server availability and EF Core configuration

Key Takeaways

Containerized .NET deployments involve several independent layers that must work together:

Domain
   |
   v
Reverse Proxy
   |
   v
Host Network / Published Port
   |
   v
.NET Application Container
   |
   v
Docker Bridge Network
   |
   v
SQL Server Container

A failure at any layer can appear as a generic application outage.

The most important troubleshooting principles are:

  1. Keep background workers from performing uncontrolled blocking or tight-loop work during startup.

  2. Use the hosting platform's supported mechanism for custom reverse-proxy configuration.

  3. Verify upstream connectivity independently from domain access.

  4. Do not use localhost to reference another container.

  5. Put communicating containers on a shared user-defined Docker network.

  6. Verify Docker port publishing and firewall rules separately.

  7. Treat firewall and Docker networking changes carefully because they can affect running containers.

Conclusion

Understanding how .NET application lifecycle behavior interacts with Docker networking, reverse proxies, and host firewalls makes multi-container deployments significantly easier to troubleshoot.

Many deployment failures that initially look like application bugs are actually caused by configuration boundaries between the host, reverse proxy, application container, and database container.

By separating background processing from startup work, using appropriate reverse-proxy configuration, connecting containers through a shared Docker network, and carefully managing firewall and NAT rules, you can diagnose these failures systematically and build more reliable enterprise .NET deployments.