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
- Navigate to your database instance
- Go to "Networking" section
- Enable "Tailscale Integration"
- Authorize Tailscale connection
- 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
- Go to Tailscale Admin Console
- Navigate to "Machines" or "Users"
- Invite team members
- They install Tailscale and authenticate
- 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.