# How to Integrate Replit with MySQL

- Tool: Replit
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with MySQL, use the mysql2 driver in Node.js or mysql-connector-python in Python, store your connection string in Replit Secrets (lock icon in sidebar), and always enable SSL for external providers. Because Replit uses dynamic IP addresses that change on restart, you must configure your MySQL host (PlanetScale, AWS RDS, or other provider) to allow connections from any IP (0.0.0.0/0) or use a static proxy.

## Why Use MySQL with Replit?

MySQL remains the most widely deployed relational database in the world, backing billions of production applications. If you are migrating an existing application to Replit, need MySQL specifically for compatibility with a framework or legacy codebase, or want the familiarity of MySQL syntax and tooling, connecting to an external MySQL provider from Replit is a straightforward process once you understand the key infrastructure constraints.

The most important constraint to understand is Replit's dynamic IP addressing. Unlike traditional hosting environments where your server has a fixed IP address, Replit's infrastructure assigns different outbound IP addresses to your app on each restart and deployment. This breaks IP-based firewall rules that databases use to restrict who can connect. The solution is to configure your MySQL provider to allow connections from all IP addresses (0.0.0.0/0) and rely on strong username/password authentication plus SSL encryption to secure the connection instead.

For new projects, Replit's built-in PostgreSQL database (10 GB free, zero configuration, Drizzle Studio included) is often the better choice. But MySQL is the right tool when you need compatibility with an existing MySQL database, are using a framework that assumes MySQL syntax (certain Laravel configurations, legacy Rails apps), need MySQL-specific features like full-text search with different tokenization, or are connecting to a managed service like PlanetScale that is purpose-built for serverless MySQL with a generous free tier.

## Before you start

- A MySQL database account on PlanetScale (free tier available), AWS RDS, Google Cloud SQL, or another provider
- Your MySQL connection credentials: host, port, database name, username, and password
- A Replit account with a Node.js or Python Repl created
- Basic familiarity with SQL and relational databases

## Step-by-step guide

### 1. Set Up Your External MySQL Database and Configure IP Access

Because Replit does not include a built-in MySQL database (only PostgreSQL is built-in), you need an external MySQL provider. The two most popular options for Replit developers are PlanetScale and AWS RDS.

For PlanetScale (recommended for new projects): Go to planetscale.com, create a free account, and create a new database. PlanetScale automatically provides a connection string with SSL enabled. In the PlanetScale dashboard, click on your database, go to 'Connect', select your framework (choose 'General'), and copy the connection details. PlanetScale does not use traditional IP whitelisting — it uses its own proxy layer, so Replit's dynamic IPs are not a problem.

For AWS RDS: Create a MySQL RDS instance in the AWS Console. Under 'Connectivity', set 'Public accessibility' to Yes. In the Security Group for your RDS instance, add an inbound rule for MySQL/Aurora (port 3306) from source 0.0.0.0/0. This allows connections from any IP, which is required because Replit's outbound IPs are dynamic and change on every restart. Secure your connection with a strong password and SSL — do not rely on IP restrictions alone.

For any provider: note your connection details:
- Host: e.g., xxx.us-east-1.rds.amazonaws.com
- Port: 3306 (default MySQL)
- Database name
- Username
- Password

Dynamic IP gotcha: Never add Replit's current IP address to a MySQL provider's IP allowlist thinking it will stay the same. Replit's outbound IPs rotate with every restart, container migration, and deployment. The IP shown in your Repl is your current address — it will change. Always use 0.0.0.0/0 or a static IP proxy service like QuotaGuard if your provider requires IP restrictions.

**Expected result:** You have MySQL connection credentials (host, port, database, username, password) from your chosen provider, and the provider is configured to accept connections from any IP or has no IP restriction requirements.

### 2. Store MySQL Credentials in Replit Secrets

Click the lock icon (🔒) in the Replit sidebar to open the Secrets panel. You have two options for storing your credentials: as a single connection string URL or as individual fields. The connection string approach is simpler for most drivers.

Add one primary secret:
- MYSQL_URL — a full connection string in the format: mysql://username:password@host:3306/database_name

For SSL connections (required by most external providers), the URL format may need additional parameters. PlanetScale connections look like: mysql://username:password@host/database?ssl={"rejectUnauthorized":true}

Alternatively, store individual secrets if you prefer:
- MYSQL_HOST
- MYSQL_PORT (usually 3306)
- MYSQL_DATABASE
- MYSQL_USER
- MYSQL_PASSWORD

For SSL certificate-based authentication (common with AWS RDS and Google Cloud SQL), you may also need to store the SSL CA certificate. Store the certificate content as a multi-line secret named MYSQL_SSL_CA.

Replit Secrets support multi-line values — paste the entire certificate including the -----BEGIN CERTIFICATE----- header and -----END CERTIFICATE----- footer as the secret value.

After saving secrets, they are immediately available as environment variables without restarting your Repl. For deployed apps, you need to redeploy after adding new secrets.

**Expected result:** MYSQL_URL (and any other secrets) appear in the Replit Secrets panel with values hidden.

### 3. Connect to MySQL with Node.js and mysql2

The mysql2 package is the recommended MySQL driver for Node.js — it is faster than the original mysql package, supports Promises natively, and handles SSL configuration cleanly. Install it in the Replit Shell: npm install mysql2

For web applications, always use a connection pool rather than creating a new connection for each request. Connection pools maintain a set of open database connections that are reused across requests, dramatically reducing the overhead of connection establishment (which includes the TCP handshake, MySQL authentication, and SSL negotiation).

The code below shows a complete database module with connection pooling and SSL support, compatible with PlanetScale, AWS RDS, and standard MySQL hosts.

```
// db.js — mysql2 connection pool with SSL
const mysql = require('mysql2/promise');

// Parse connection config from environment
function getConnectionConfig() {
  // Option 1: Full connection URL
  if (process.env.MYSQL_URL) {
    return {
      uri: process.env.MYSQL_URL,
      ssl: { rejectUnauthorized: true },
      waitForConnections: true,
      connectionLimit: 10,
      queueLimit: 0
    };
  }

  // Option 2: Individual credentials
  return {
    host: process.env.MYSQL_HOST,
    port: parseInt(process.env.MYSQL_PORT || '3306'),
    database: process.env.MYSQL_DATABASE,
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    // SSL is required for external providers
    ssl: process.env.MYSQL_SSL_CA
      ? { ca: process.env.MYSQL_SSL_CA, rejectUnauthorized: true }
      : { rejectUnauthorized: false }, // Self-signed certs: set to false
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
  };
}

// Create pool lazily on first use
let pool = null;

function getPool() {
  if (!pool) {
    pool = mysql.createPool(getConnectionConfig());
  }
  return pool;
}

// Execute a query with automatic connection management
async function query(sql, params = []) {
  const connection = await getPool().getConnection();
  try {
    const [rows, fields] = await connection.execute(sql, params);
    return rows;
  } finally {
    connection.release(); // Always release back to pool
  }
}

// Test the connection
async function testConnection() {
  try {
    const result = await query('SELECT 1 as test');
    console.log('MySQL connected successfully:', result[0]);
    return true;
  } catch (err) {
    console.error('MySQL connection failed:', err.message);
    return false;
  }
}

module.exports = { query, testConnection, getPool };
```

**Expected result:** Calling testConnection() logs 'MySQL connected successfully' and returns true. The connection pool is ready to handle queries.

### 4. Connect to MySQL with Python

For Python projects, install either mysql-connector-python (official Oracle driver) or PyMySQL (pure Python, easier to install). In the Replit Shell, run: pip install mysql-connector-python

The code below shows a Flask server with MySQL integration using mysql-connector-python and connection pooling. For simpler scripts or data processing, PyMySQL works identically but has a different import name: import pymysql instead of import mysql.connector.

For PlanetScale connections in Python, ssl_disabled should be False and ssl_verify_cert should be True to enforce SSL. For self-hosted MySQL with a self-signed certificate, set ssl_verify_cert to False.

```
# app.py — Python Flask with MySQL (mysql-connector-python)
import os
import mysql.connector
from mysql.connector import pooling
from flask import Flask, request, jsonify

app = Flask(__name__)

# Build connection config from environment
def get_db_config():
    db_url = os.environ.get('MYSQL_URL')
    if db_url:
        # Parse mysql://user:pass@host:port/database
        from urllib.parse import urlparse
        parsed = urlparse(db_url)
        return {
            'host': parsed.hostname,
            'port': parsed.port or 3306,
            'database': parsed.path.lstrip('/'),
            'user': parsed.username,
            'password': parsed.password,
            'ssl_disabled': False,
            'ssl_verify_cert': True
        }
    # Individual environment variables
    return {
        'host': os.environ['MYSQL_HOST'],
        'port': int(os.environ.get('MYSQL_PORT', 3306)),
        'database': os.environ['MYSQL_DATABASE'],
        'user': os.environ['MYSQL_USER'],
        'password': os.environ['MYSQL_PASSWORD'],
        'ssl_disabled': False,
        'ssl_verify_cert': True
    }

# Create connection pool
try:
    db_config = get_db_config()
    connection_pool = mysql.connector.pooling.MySQLConnectionPool(
        pool_name='replit_pool',
        pool_size=5,
        **db_config
    )
    print('MySQL connection pool created')
except Exception as e:
    print(f'Failed to create MySQL pool: {e}')
    connection_pool = None

def execute_query(sql, params=None):
    """Execute a query using a pooled connection."""
    conn = connection_pool.get_connection()
    try:
        cursor = conn.cursor(dictionary=True)
        cursor.execute(sql, params or ())
        if sql.strip().upper().startswith('SELECT'):
            return cursor.fetchall()
        else:
            conn.commit()
            return {'affected_rows': cursor.rowcount,
                    'last_insert_id': cursor.lastrowid}
    finally:
        cursor.close()
        conn.close()  # Returns to pool

@app.route('/health', methods=['GET'])
def health():
    try:
        result = execute_query('SELECT VERSION() as version')
        return jsonify({'status': 'ok', 'mysql_version': result[0]['version']})
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/users', methods=['GET'])
def get_users():
    try:
        users = execute_query('SELECT id, name, email, created_at FROM users LIMIT 100')
        return jsonify({'users': users})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/users', methods=['POST'])
def create_user():
    data = request.json
    try:
        result = execute_query(
            'INSERT INTO users (name, email) VALUES (%s, %s)',
            (data['name'], data['email'])
        )
        return jsonify({'id': result['last_insert_id'], 'success': True})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
```

**Expected result:** GET /health returns a 200 response with the MySQL server version, confirming the SSL connection is working correctly.

### 5. Deploy and Handle Production Connection Concerns

For production deployment, click the Deploy button in the Replit top-right corner and choose 'Autoscale' for web APIs that handle variable traffic, or 'Reserved VM' for apps that need consistent low-latency database access (Reserved VMs stay warm and avoid connection pool recreation overhead).

For production MySQL connections, there are three critical concerns beyond basic connectivity:

1. Connection pool sizing: The default pool size of 10 is appropriate for most Replit workloads. MySQL's default max_connections is 151 — if you run multiple Replit deployments or autoscale instances, each instance creates its own pool. Calculate your total connections as (instances × pool_size) and ensure it is below max_connections minus a buffer for admin connections.

2. Connection timeouts: MySQL closes idle connections after wait_timeout (default 8 hours on most providers). Implement a keepalive mechanism or handle reconnection gracefully. The mysql2 pool handles reconnection automatically; for Python's mysql-connector-python, implement retry logic.

3. SSL requirement: All external MySQL providers (PlanetScale, AWS RDS, Google Cloud SQL) require SSL in production. Never disable SSL for connections traveling over the public internet.

```
// Production-ready Express server with MySQL
const express = require('express');
const { query, testConnection } = require('./db');

const app = express();
app.use(express.json());

// Test DB on startup
testConnection().then(ok => {
  if (!ok) {
    console.error('Database connection failed on startup');
    // In production, you may want to exit:
    // process.exit(1);
  }
});

// Example: paginated users endpoint
app.get('/users', async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = Math.min(parseInt(req.query.limit) || 20, 100);
    const offset = (page - 1) * limit;

    const users = await query(
      'SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?',
      [limit, offset]
    );
    const [countResult] = await query('SELECT COUNT(*) as total FROM users');

    res.json({
      users,
      pagination: {
        page,
        limit,
        total: countResult.total,
        pages: Math.ceil(countResult.total / limit)
      }
    });
  } catch (err) {
    console.error('Query error:', err.message);
    res.status(500).json({ error: 'Database query failed' });
  }
});

// Graceful shutdown — close pool on process exit
process.on('SIGTERM', async () => {
  console.log('Shutting down, closing database pool...');
  await require('./db').getPool().end();
  process.exit(0);
});

app.listen(3000, '0.0.0.0', () => {
  console.log('Server running on port 3000');
});
```

**Expected result:** Your deployed Replit app connects to MySQL successfully and GET /users returns paginated results from the database.

## Best practices

- Never hardcode MySQL credentials in your code — store the complete connection string as MYSQL_URL in Replit Secrets (lock icon 🔒). Replit's Secret Scanner detects connection strings in code files and will warn you, but prevention is better than detection.
- Always enable SSL for external MySQL connections by setting ssl: { rejectUnauthorized: true } in Node.js or ssl_disabled: False in Python — data in transit is vulnerable without SSL, especially because you cannot use IP restrictions with Replit's dynamic IPs.
- Configure your MySQL provider to allow connections from 0.0.0.0/0 (all IPs) instead of trying to whitelist Replit's current IP — Replit's outbound IPs change on every restart and deployment, making static whitelisting impossible and fragile.
- Use connection pooling (mysql.createPool() in Node.js, MySQLConnectionPool in Python) rather than creating new connections per request — connection establishment includes TCP handshake, authentication, and SSL negotiation, adding 50-200ms latency if done on every request.
- Always use parameterized queries (execute with placeholders) rather than string concatenation for user input — SQL injection is still one of the most common security vulnerabilities, and parameterized queries prevent it completely.
- Deploy on Reserved VM if your app makes many database calls and latency matters — Autoscale deployments that scale to zero reinitialize the connection pool on cold start, adding latency to the first request after an idle period.
- Consider PlanetScale for new MySQL projects on Replit — it has no IP restriction requirements, a generous free tier, automatic failover, and the serverless driver (serverless driver for JS) that works well with Replit's serverless nature.

## Use cases

### Migrating an Existing MySQL Application to Replit

Move an existing web app that uses MySQL to Replit by exporting your database schema and data, importing it to PlanetScale or AWS RDS, and updating your connection string environment variable. Your application code continues working without changes since mysql2 uses the same API as the mysql package.

Prompt example:

```
Build a Node.js Express REST API that connects to a MySQL database using the mysql2 package. Create endpoints for GET /users (list all users), POST /users (create user with name, email, role), GET /users/:id, and DELETE /users/:id. Use connection pooling and store DATABASE_URL in environment variables.
```

### PlanetScale Serverless Database Backend

Build a Replit API that uses PlanetScale as its MySQL backend, taking advantage of PlanetScale's free tier and serverless scaling. PlanetScale's branching workflow lets you test schema changes safely before deploying them to production.

Prompt example:

```
Create a Python Flask API that connects to PlanetScale MySQL using mysql-connector-python with SSL. Implement a /products endpoint that reads from a products table with id, name, price, and stock columns. Include proper connection pooling and error handling for database connection failures.
```

### Analytics Dashboard Powered by MySQL

Build a read-only analytics dashboard on Replit that queries an existing MySQL database (AWS RDS, production MySQL server) to display business metrics, reports, or data visualizations without exposing direct database access to end users.

Prompt example:

```
Create a Node.js Express API server that makes read-only MySQL queries against an analytics database. Add a /stats endpoint that runs aggregation queries (COUNT, SUM, GROUP BY date) and returns JSON results for a frontend chart library. Implement query result caching to reduce database load.
```

## Troubleshooting

### Error: 'ECONNREFUSED' or 'connect ETIMEDOUT' when connecting to MySQL

Cause: The most common causes are: the MySQL host is blocking Replit's IP because IP allowlisting is not set to 0.0.0.0/0, the MySQL port 3306 is not open in the security group or firewall, or the host/port in your connection string is incorrect.

Solution: In your MySQL provider's network settings, confirm the IP allowlist includes 0.0.0.0/0 (all IPs) — Replit's dynamic IPs make static IP whitelisting impossible. For AWS RDS, check the Security Group has an inbound rule for port 3306 from 0.0.0.0/0. For PlanetScale, no IP configuration is needed. Test connectivity from the Replit Shell: nc -zv your-mysql-host 3306 should show 'Connection to host succeeded'.

### Error: 'SSL connection error' or 'ER_NOT_SUPPORTED_AUTH_MODE'

Cause: SSL connection error usually means the driver is trying to connect without SSL but the server requires it, or the SSL certificate validation is failing. ER_NOT_SUPPORTED_AUTH_MODE means the MySQL user is configured with caching_sha2_password authentication but the driver does not support it.

Solution: For SSL errors: ensure ssl: { rejectUnauthorized: true } is set in your connection config (Node.js) or ssl_disabled: False (Python). For certificate errors with self-signed certs, set rejectUnauthorized: false. For auth mode errors, either update the MySQL user to use mysql_native_password authentication (ALTER USER 'user'@'%' IDENTIFIED WITH mysql_native_password BY 'password') or upgrade your driver version.

```
// Node.js: explicit SSL config for external providers
const pool = mysql.createPool({
  host: process.env.MYSQL_HOST,
  user: process.env.MYSQL_USER,
  password: process.env.MYSQL_PASSWORD,
  database: process.env.MYSQL_DATABASE,
  ssl: { rejectUnauthorized: true } // Enable SSL
});
```

### Error: 'Too many connections' (MySQL error 1040)

Cause: The total number of connections from all Replit instances exceeds MySQL's max_connections limit. This commonly happens when Autoscale creates multiple instances, each initializing a connection pool, or when connections are not being released back to the pool correctly.

Solution: Reduce your pool size in the connection config (connectionLimit: 3 instead of 10 for Autoscale deployments). Ensure connections are always released back to the pool using connection.release() in a finally block. Use a single pool instance per process (the lazy singleton pattern in the db.js example) rather than creating multiple pools.

```
// Reduce pool size for Autoscale deployments
const pool = mysql.createPool({
  connectionLimit: 3, // Smaller pool for serverless/autoscale
  waitForConnections: true,
  queueLimit: 0,
  // ... other config
});
```

### Queries work in development but fail after deployment

Cause: Deployment secrets are separate from workspace secrets in older Replit versions. New secrets added after the last deploy are not available to the running deployment until you redeploy.

Solution: After adding or updating secrets in the Replit Secrets panel, click Deploy > Redeploy to push the new secrets to the running deployment. In Replit as of December 2025, workspace and deployment secrets sync automatically, but triggering a manual redeploy is always a safe way to confirm secrets are current.

## Frequently asked questions

### How do I connect Replit to MySQL?

Install mysql2 (Node.js: npm install mysql2) or mysql-connector-python (Python: pip install mysql-connector-python), store your connection string in Replit Secrets as MYSQL_URL (lock icon in sidebar), and create a connection pool using the credentials. Always enable SSL by setting rejectUnauthorized: true in Node.js or ssl_disabled: False in Python. Configure your MySQL provider to accept connections from all IPs (0.0.0.0/0) because Replit uses dynamic outbound addresses.

### Why does Replit need 0.0.0.0/0 for MySQL access?

Replit does not provide static outbound IP addresses. Your app's IP changes every time the Repl restarts, a new deployment is created, or Replit migrates your container. This makes it impossible to whitelist a specific IP in MySQL's network access controls. The solution is to allow all IPs (0.0.0.0/0) and rely on strong password authentication and SSL encryption to secure the connection instead.

### Does Replit have a built-in MySQL database?

No. Replit includes a built-in PostgreSQL database (10 GB free, zero setup) but not MySQL. For MySQL, you need an external provider such as PlanetScale, AWS RDS, Google Cloud SQL, Railway, or Aiven. PlanetScale is the easiest option for Replit developers because it has no IP restriction requirements — its proxy-based architecture handles the dynamic IP problem automatically.

### How do I store MySQL credentials securely in Replit?

Open Replit Secrets (lock icon 🔒 in the sidebar) and add MYSQL_URL with your full connection string: mysql://username:password@host:3306/database. Replit encrypts this with AES-256 and injects it as an environment variable. Access it as process.env.MYSQL_URL in Node.js or os.environ['MYSQL_URL'] in Python. Never paste connection strings directly into your code files.

### What is the best MySQL deployment type for Replit?

For web APIs and apps with variable traffic, use Autoscale deployment — it scales automatically and only charges for active compute time. For apps with persistent database connections that must minimize cold-start latency (bots, data processing pipelines), use Reserved VM because Autoscale instances that scale to zero reinitialize the connection pool on each cold start, adding 200-500ms to the first request.

### Can I use PlanetScale with Replit?

Yes, PlanetScale works extremely well with Replit. Unlike other MySQL providers, PlanetScale does not require IP allowlisting — it uses a proxy architecture that accepts connections from any IP. Get your connection string from the PlanetScale dashboard under Connect > Connect with > Node.js or Python, enable SSL in the connection config, and store the credentials in Replit Secrets. The free tier (currently available as a hobby plan) is sufficient for development and small production apps.

---

Source: https://www.rapidevelopers.com/replit-integration/mysql
© RapidDev — https://www.rapidevelopers.com/replit-integration/mysql
