Skip to main content

Private Networking with Tailscale for Secure Database Access

Exposing your database to the public internet is a security risk. Even with strong passwords and firewall rules, public endpoints are attack targets. Private networking provides a secure way to access your database without exposing it publicly.

๐ŸŽฏ Why Private Networking?โ€‹

Security Benefitsโ€‹

  • No public exposure: Database not accessible from the internet
  • Encrypted traffic: All traffic encrypted end-to-end
  • Zero-trust model: Only authorized devices can connect
  • Audit trail: Track all access attempts

Use Casesโ€‹

  • Development teams: Secure access for remote developers
  • CI/CD pipelines: Automated deployments without public endpoints
  • Monitoring tools: Secure access for observability systems
  • Backup systems: Secure backup connections

๐Ÿ” What is Tailscale?โ€‹

Tailscale creates a secure mesh VPN using WireGuard, allowing devices to connect as if they're on the same local network, regardless of their physical location.

Key Featuresโ€‹

  • Zero-config VPN: Automatic setup and key management
  • Cross-platform: Works on Linux, macOS, Windows, iOS, Android
  • Mesh networking: Direct peer-to-peer connections
  • Access control: Fine-grained permissions
  • Audit logs: Track all connections

๐Ÿš€ Setting Up Tailscaleโ€‹

Step 1: Install Tailscaleโ€‹

On Linux (Database Server):

# Add Tailscale repository
curl -fsSL https://tailscale.com/install.sh | sh

# Start Tailscale
sudo tailscale up

On macOS:

brew install tailscale
sudo tailscale up

On Windows:

Download from tailscale.com/download

Step 2: Authenticateโ€‹

When you run tailscale up, you'll get a URL to authenticate:

To authenticate, visit:
https://login.tailscale.com/admin/a/...

Open the URL in your browser and sign in with your Google, Microsoft, or GitHub account.

Step 3: Verify Connectionโ€‹

# Check Tailscale status
tailscale status

# Test connectivity
ping <database-server-tailscale-ip>

๐Ÿ—„๏ธ Configuring MariaDB for Tailscaleโ€‹

Step 1: Find Tailscale IPโ€‹

On your database server:

tailscale ip -4
# Output: 100.x.x.x

Step 2: Configure MariaDB Bindingโ€‹

Edit MariaDB configuration:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

Update bind address:

[mysqld]
bind-address = 100.x.x.x # Tailscale IP
# Or bind to all interfaces (less secure)
# bind-address = 0.0.0.0

Restart MariaDB:

sudo systemctl restart mariadb

Step 3: Verify MariaDB is Listeningโ€‹

# Check if MariaDB is listening on Tailscale IP
sudo netstat -tlnp | grep 3306

# Should show:
# tcp 0 0 100.x.x.x:3306 0.0.0.0:* LISTEN 1234/mysqld

๐Ÿ”ง Configuring Filess.io with Tailscaleโ€‹

On Filess.io Dedicated Runtime, you can enable Tailscale integration:

Via Consoleโ€‹

  1. Navigate to your database instance
  2. Go to "Networking" section
  3. Enable "Tailscale Integration"
  4. Authorize Tailscale connection
  5. Note the Tailscale IP address

Via APIโ€‹

# Visual example of Tailscale configuration
# (Imagine a beautiful UI here)

Get Tailscale Connection Detailsโ€‹

# Placeholder for API example

Response:

{
"enabled": true,
"tailscale_ip": "100.x.x.x",
"connection_string": "mariadb://user:[email protected]:3306/dbname"
}

๐Ÿ’ป Connecting from Applicationโ€‹

Update Connection Stringโ€‹

Update your .env file:

# Before (public endpoint)
DATABASE_URL="mysql://user:[email protected]:3306/filess_example"

# After (Tailscale private network)
DATABASE_URL="mysql://user:[email protected]:3306/filess_example"

Test Connectionโ€‹

# From your development machine (with Tailscale installed)
mysql -h 100.x.x.x -u user -p filess_example

Application Code (No Changes Needed)โ€‹

Your application code doesn't need changes - just update the connection string:

// src/index.ts
// Connection string from .env automatically used by Prisma
export const prisma = new PrismaClient();

๐Ÿข Team Access Managementโ€‹

Adding Team Membersโ€‹

  1. Go to Tailscale Admin Console
  2. Navigate to "Machines" or "Users"
  3. Invite team members
  4. They install Tailscale and authenticate
  5. They can now access the database via Tailscale IP

Access Control Lists (ACLs)โ€‹

Create ACL rules in Tailscale:

{
"groups": {
"group:developers": ["[email protected]", "[email protected]"],
"group:ops": ["[email protected]"]
},
"hosts": {
"database": "100.x.x.x"
},
"acls": [
{
"action": "accept",
"src": ["group:developers"],
"dst": ["database:3306"]
},
{
"action": "accept",
"src": ["group:ops"],
"dst": ["database:*"]
}
]
}

Device Tagsโ€‹

Tag devices for easier management:

# Tag a device
tailscale set --advertise-tags=tag:database-server

# In ACL, reference by tag
{
"acls": [
{
"action": "accept",
"src": ["tag:developer-laptops"],
"dst": ["tag:database-server:3306"]
}
]
}

๐Ÿ”„ CI/CD Integrationโ€‹

GitHub Actionsโ€‹

# .github/workflows/test.yml
name: Run Tests

on: [push]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install Tailscale
run: |
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --authkey=${{ secrets.TAILSCALE_AUTH_KEY }}

- name: Wait for Tailscale
run: sleep 10

- name: Run tests
env:
DATABASE_URL: mariadb://user:[email protected]:3306/filess_example
run: npm test

GitLab CIโ€‹

# .gitlab-ci.yml
test:
image: node:20
before_script:
- curl -fsSL https://tailscale.com/install.sh | sh
- sudo tailscale up --authkey=$TAILSCALE_AUTH_KEY
- sleep 10
script:
- npm test
variables:
DATABASE_URL: "mariadb://user:[email protected]:3306/filess_example"

๐Ÿ” Monitoring and Debuggingโ€‹

Check Tailscale Statusโ€‹

# On database server
tailscale status

# Output:
# 100.x.x.x database-server [email protected] linux -
# 100.y.y.y dev-laptop [email protected] macOS -

Test Connectivityโ€‹

# Ping test
ping 100.x.x.x

# Port test
nc -zv 100.x.x.x 3306

# MySQL connection test
mysql -h 100.x.x.x -u user -p -e "SELECT 1"

View Tailscale Logsโ€‹

# System logs
sudo journalctl -u tailscaled -f

# Tailscale status with details
tailscale status --json | jq

๐Ÿ›ก๏ธ Security Best Practicesโ€‹

1. Use Tailscale ACLsโ€‹

Restrict access to only necessary devices:

{
"acls": [
{
"action": "accept",
"src": ["tag:authorized-devices"],
"dst": ["tag:database-server:3306"]
}
]
}

2. Rotate Keys Regularlyโ€‹

# Generate new auth key
# In Tailscale admin console: Settings > Keys

# Use new key
tailscale up --authkey=<new-key>

3. Monitor Accessโ€‹

Enable audit logs in Tailscale admin console to track all connections.

4. Use Database Firewallโ€‹

Even on private network, use MariaDB's built-in firewall:

-- Allow only specific Tailscale IPs
CREATE TABLE mysql.firewall_users (
USERHOST VARCHAR(80) PRIMARY KEY,
RULE TEXT
);

INSERT INTO mysql.firewall_users VALUES
('[email protected]', 'ALLOW'),
('[email protected]', 'ALLOW');

๐Ÿ”„ Failover and High Availabilityโ€‹

Multiple Database Serversโ€‹

With Tailscale, you can easily connect to multiple database servers:

// src/config/database.ts
const databaseConfig = {
primary: {
host: process.env.DB_PRIMARY_TAILSCALE_IP,
port: 3306
},
replica: {
host: process.env.DB_REPLICA_TAILSCALE_IP,
port: 3306
}
};

Load Balancingโ€‹

Use Tailscale's built-in load balancing or configure at application level:

// Round-robin connection selection
const getDatabaseConnection = () => {
const servers = [config.primary, config.replica];
const index = Math.floor(Math.random() * servers.length);
return servers[index];
};

๐Ÿ“Š Performance Considerationsโ€‹

Latencyโ€‹

Tailscale uses direct peer-to-peer connections when possible, minimizing latency:

# Test latency
ping 100.x.x.x

# Typical results:
# - Same region: <10ms
# - Different regions: 20-50ms
# - Different continents: 50-150ms

Bandwidthโ€‹

Tailscale traffic is encrypted but efficient:

  • Overhead: ~4 bytes per packet
  • Encryption: WireGuard (fast, modern encryption)
  • Compression: Not applied (database traffic is already compressed)

๐Ÿงช Testing Private Network Setupโ€‹

Test Scriptโ€‹

#!/bin/bash
# test-tailscale-connection.sh

DB_HOST="100.x.x.x"
DB_USER="test_user"
DB_PASS="test_password"
DB_NAME="filess_example"

echo "Testing Tailscale connection to database..."

# Test 1: Ping
if ping -c 1 $DB_HOST > /dev/null 2>&1; then
echo "โœ“ Ping successful"
else
echo "โœ— Ping failed"
exit 1
fi

# Test 2: Port connectivity
if nc -zv $DB_HOST 3306 > /dev/null 2>&1; then
echo "โœ“ Port 3306 accessible"
else
echo "โœ— Port 3306 not accessible"
exit 1
fi

# Test 3: MySQL connection
if mysql -h $DB_HOST -u $DB_USER -p$DB_PASS -e "SELECT 1" $DB_NAME > /dev/null 2>&1; then
echo "โœ“ MySQL connection successful"
else
echo "โœ— MySQL connection failed"
exit 1
fi

echo "All tests passed!"

๐ŸŽฏ Integration with Example Appโ€‹

Update your application to use Tailscale:

// src/utils/database-health.ts
export async function checkDatabaseHealth() {
try {
await prisma.$queryRaw`SELECT 1`;
return {
status: 'healthy',
network: 'tailscale',
latency: await measureLatency()
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message
};
}
}

โœ… Checklistโ€‹

  • Tailscale installed on database server
  • Tailscale installed on development machines
  • MariaDB configured to listen on Tailscale IP
  • Connection strings updated
  • ACLs configured for access control
  • Team members added to Tailscale
  • CI/CD pipelines updated
  • Monitoring and logging enabled
  • Backup connections tested
  • Documentation updated

๐Ÿ”ฎ Next Stepsโ€‹

  • Subnet routing: Route entire subnets through Tailscale
  • Exit nodes: Use Tailscale as VPN for internet traffic
  • MagicDNS: Use friendly hostnames instead of IPs
  • SSH access: Combine with SSH for additional security

With Tailscale configured, your database is accessible only to authorized devices on your private network, significantly improving security without sacrificing convenience.