When deploying a production .NET application alongside Microsoft SQL Server on a self-hosted Ubuntu Linux server using Docker, security and data persistence are top priorities. Out of the box, exposing database ports to the public internet invites aggressive automated botnets and brute-force attacks.

In this guide, we will walk through how to harden your network with iptables, ensure database persistence using Docker volumes, automate regular backups, protect your SSH access with Fail2Ban, and set up a lightweight Telegram alerting system to monitor container health in real time.

1. The Challenge: Combating Database Brute-Force Attacks

When you publish port 1433 (SQL Server) to the public internet for remote management or external integrations, bots scan your IP address almost instantly. Reviewing your container logs (docker logs sqlserver) will often reveal thousands of failed login attempts:

Plaintext

2026-09-30 07:41:00.90 Logon      Error: 18456, Severity: 14, State: 8.
2026-09-30 07:41:00.90 Logon      Login failed for user 'sa'. Reason: Password did not match that for the login provided. [CLIENT: 130.94.12.51]

To stop this traffic, you need to lock down the network layer before connection requests ever reach the database engine.

2. Hardening Network Security with iptables and Docker

Docker bypasses standard UFW (Uncomplicated Firewall) rules by manipulating iptables directly through the DOCKER-USER chain. We can leverage this chain to block external traffic to port 1433 while explicitly permitting local container communication and trusted management IPs.

Step 1: Configure the DOCKER-USER Chain

Run the following commands on your Ubuntu host to structure your firewall rules:

Bash

# 1. Allow your specific trusted management IP (e.g., your local office/home static IP)
sudo iptables -A DOCKER-USER -p tcp --dport 1433 -s YOUR_MANAGEMENT_IP -j RETURN

# 2. Allow internal Docker bridge networks so your ASP.NET Core web app can talk to SQL Server
sudo iptables -A DOCKER-USER -p tcp --dport 1433 -s 172.16.0.0/12 -j ACCEPT

# 3. Drop all other external public traffic targeting port 1433
sudo iptables -A DOCKER-USER -p tcp --dport 1433 -j DROP

Step 2: Make Firewall Rules Persistent

Ubuntu does not save iptables rules across reboots by default. Install iptables-persistent to lock them in permanently:

Bash

sudo apt update && sudo apt install iptables-persistent -y
sudo netfilter-persistent save

Verify your firewall rules at any time using:

Bash

sudo iptables -L DOCKER-USER -n -v --line-numbers

3. Ensuring Data Persistence with Docker Volumes

Running SQL Server inside a container without a named volume means your data lives in an ephemeral layer that can be lost during container updates.

Step 1: Back Up Your Existing Database

Before making changes, generate a manual backup via your container:

Bash

docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'YourStrongPassword123!' -C -Q "BACKUP DATABASE [YourDBName] TO DISK = N'/var/opt/mssql/data/backup.bak' WITH FORMAT;"
docker cp sqlserver:/var/opt/mssql/data/backup.bak /home/ubuntu/sql_backups/backup.bak

Step 2: Recreate the Container with a Persistent Volume

Stop and remove the old non-persistent container, create a Docker managed volume, and spin up a fresh instance:

Bash

docker stop sqlserver
docker rm sqlserver

# Create a persistent Docker volume
docker volume create mssql_data

# Deploy the new persistent container
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YourStrongPassword123!" \
  -p 1433:1433 --name sqlserver --restart unless-stopped \
  -v mssql_data:/var/opt/mssql \
  -d mcr.microsoft.com/mssql/server:2022-latest

Restore your database into the new container using sqlcmd, and restart your ASP.NET Core application container to re-establish the connection pool.

4. Automating Database Backups with Cron

To prevent data loss over time, create an automated backup script (/home/ubuntu/db_backup.sh):

Bash

#!/bin/bash
BACKUP_DIR="/home/ubuntu/sql_backups"
DATE=$(date +%Y%m%d_%H%M%S)
FILENAME="prod_backup_$DATE.bak"

docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'YourStrongPassword123!' -C -Q "BACKUP DATABASE [YourDBName] TO DISK = N'/var/opt/mssql/data/$FILENAME';"
docker cp sqlserver:/var/opt/mssql/data/$FILENAME $BACKUP_DIR/$FILENAME

# Optional: Delete backups older than 7 days
find $BACKUP_DIR -name "*.bak" -mtime +7 -delete

Make it executable (chmod +x /home/ubuntu/db_backup.sh) and schedule it in crontab (crontab -e) to run every 6 hours:

Code snippet

0 */6 * * * /home/ubuntu/db_backup.sh >> /home/ubuntu/sql_backups/cron_backup.log 2>&1

5. Securing SSH Access and Installing Fail2Ban

Botnets don't just target databases—they constantly hammer standard SSH ports (22).

  1. Harden SSH Config (/etc/ssh/sshd_config):

    • Change the default port: Port 2222

    • Disable root login: PermitRootLogin no

    • Enforce SSH keys: PasswordAuthentication no

  2. Restart SSH: sudo systemctl restart ssh

  3. Install Fail2Ban to automatically ban IPs that repeatedly fail authentication:

    Bash

    sudo apt install fail2ban -y
    sudo systemctl enable --now fail2ban
    

6. Building a Lightweight Telegram Monitoring Script

To ensure you are notified immediately if a container goes down or if brute-force attacks surge, create a shell script (/home/ubuntu/system_monitor.sh) that hooks into the Telegram Bot API:

Bash

#!/bin/bash
TOKEN="YOUR_TELEGRAM_BOT_TOKEN"
CHAT_ID="YOUR_CHAT_ID"
MESSAGE=""

# 1. Check if critical containers are down
DOWN_CONTAINERS=$(docker ps -a --format '{{.Names}}: {{.State}}' | grep -E "sqlserver|web-app" | grep -v "running")
if [ -n "$DOWN_CONTAINERS" ]; then
    MESSAGE="🚨 *Docker Alert:* Container offline!\n$DOWN_CONTAINERS\n"
fi

# 2. Check for SQL login failures in the last 5 minutes
SQL_FAILS=$(docker logs --since 5m sqlserver 2>&1 | grep -c "Login failed")
if [ "$SQL_FAILS" -gt 5 ]; then
    MESSAGE="${MESSAGE}⚠️ *Database Alert:* $SQL_FAILS brute-force login attempts detected!\n"
fi

# Send alert if message is populated
if [ -n "$MESSAGE" ]; then
    curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" \
         -d chat_id="$CHAT_ID" \
         -d text="$MESSAGE" \
         -d parse_mode="Markdown" > /dev/null
fi

Schedule this script in crontab to run every 5 minutes (*/5 * * * * /home/ubuntu/system_monitor.sh), and you will have a proactive, automated notification system safeguarding your infrastructure 24/7.

Conclusion

By combining iptables DOCKER-USER restrictions, persistent Docker volumes, automated cron backups, SSH hardening with Fail2Ban, and Telegram monitoring, your self-hosted .NET application and SQL Server stack will be robust, secure, and ready for production traffic.