webmavens/laravel-remote-http-database

Laravel database adapter for remote MySQL via HTTP API with encryption

Installs: 43

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/webmavens/laravel-remote-http-database

0.0.4 2026-02-02 03:24 UTC

This package is auto-updated.

Last update: 2026-02-02 03:25:45 UTC


README

A Laravel package that provides a custom database adapter for communicating with a remote MySQL server via HTTP API. This allows you to run a Laravel application on a server without MySQL by connecting to a remote MySQL server through an encrypted HTTP endpoint.

Quick Start

  1. Install on both servers: composer require webmavens/laravel-remote-http-database
  2. Generate keys (run once, use on both servers):
    php -r "echo 'REMOTE_DB_API_KEY=' . bin2hex(random_bytes(32)) . PHP_EOL;"
    php -r "echo 'REMOTE_DB_ENCRYPTION_KEY=' . base64_encode(random_bytes(32)) . PHP_EOL;"
  3. Server 1 (has MySQL): Set REMOTE_DB_API_KEY and REMOTE_DB_ENCRYPTION_KEY in .env (optionally add REMOTE_DB_ALLOWED_IPS for IP whitelisting)
  4. Server 2 (client):
    • ⚠️ REQUIRED: Manually add the remote-http connection to config/database.php (see Step 4 below for the exact code)
    • Set DB_CONNECTION=remote-http, DB_REMOTE_ENDPOINT, DB_REMOTE_API_KEY, and DB_REMOTE_ENCRYPTION_KEY in .env

See the Installation section for detailed steps.

Features

  • πŸ”’ Secure: AES-256-GCM encryption for all data in transit
  • πŸ”‘ Authenticated: API key-based authentication
  • πŸ›‘οΈ IP Whitelisting: Optional IP address whitelisting for additional security
  • πŸ”„ Transaction Support: Full transaction support with session-based state management
  • πŸš€ Drop-in Replacement: No code changes needed - just change your database connection
  • βœ… Full Compatibility: Supports all Laravel database features (Eloquent, migrations, transactions, etc.)

Architecture

Server 2 (No MySQL)                    Server 1 (Has MySQL)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Laravel Application β”‚                β”‚ Remote Endpoint     β”‚
β”‚                     β”‚                β”‚                     β”‚
β”‚  Remote HTTP        │──HTTPS/API───▢│  MySQL Database     β”‚
β”‚  Database Adapter   β”‚                β”‚                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Requirements

  • PHP 7.4 or higher (tested up to PHP 8.5+)
  • Laravel 10.x, 11.x, or 12.x
  • Guzzle HTTP Client
  • OpenSSL extension (for encryption)

Installation

Step 1: Install the Package

Install the package on both servers via Composer:

composer require webmavens/laravel-remote-http-database

The service provider will be auto-discovered by Laravel, so no manual registration is needed.

Step 2: Generate Security Keys

Generate a secure API key and encryption key. These must be identical on both servers:

# Generate a 32-byte encryption key (base64 encoded)
php -r "echo base64_encode(random_bytes(32));"

# Generate a secure API key (hex encoded, 64 characters)
php -r "echo bin2hex(random_bytes(32));"

Save these values - you'll need them for both servers.

Step 3: Configure Server 1 (Has MySQL)

Server 1 hosts the MySQL database and serves the remote endpoint.

  1. Configure your .env file with your MySQL connection and security keys:
# Standard MySQL connection (for the endpoint to use)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database
DB_USERNAME=your_username
DB_PASSWORD=your_password

# Remote endpoint security keys (MUST match Server 2)
REMOTE_DB_API_KEY=your-generated-api-key-here
REMOTE_DB_ENCRYPTION_KEY=your-generated-encryption-key-here

# Optional: IP whitelist (comma-separated IP addresses)
# Only requests from these IPs will be allowed. Leave empty to allow all IPs.
REMOTE_DB_ALLOWED_IPS=192.168.1.100,10.0.0.50,203.0.113.45

Important Notes:

  • The REMOTE_DB_API_KEY can be any secure string (recommended: 64+ characters)
  • The REMOTE_DB_ENCRYPTION_KEY must be exactly 32 bytes when decoded. You can store it as:
    • Raw 32-byte string, OR
    • Base64-encoded string (44 characters) - the package will auto-detect and decode it
  • IP Whitelisting: The REMOTE_DB_ALLOWED_IPS is optional. If set, only requests from the specified IP addresses will be allowed. Requests from other IPs will be rejected with a 403 Forbidden response before any processing occurs. This provides an additional security layer.
  1. The endpoint route is automatically registered at /remote-db-endpoint when REMOTE_DB_API_KEY is set.

  2. Test the endpoint (optional):

curl http://localhost:8000/remote-db-endpoint

You should see a JSON response with endpoint information.

Step 4: Configure Server 2 (No MySQL - Client)

Server 2 connects to Server 1's endpoint to access the database.

⚠️ IMPORTANT: Manual code edit required! You must manually add the remote-http connection configuration to your config/database.php file. This cannot be done automatically.

  1. Add the remote-http connection to config/database.php:

Open config/database.php and add the following connection to the connections array. Important: This configuration safely handles cases where DB_REMOTE_ENDPOINT is not set (e.g., on Server 1), preventing PHP deprecation warnings:

'connections' => [
    // ... other connections
    
    'remote-http' => [
        'driver' => 'remote-http',
        'endpoint' => env('DB_REMOTE_ENDPOINT'),
        'api_key' => env('DB_REMOTE_API_KEY'),
        'encryption_key' => (function() {
            $key = env('DB_REMOTE_ENCRYPTION_KEY');
            // Return null if key is not set (prevents deprecation warning on Server 1)
            if ($key === null) {
                return null;
            }
            // Try to decode base64, fallback to raw key if decoding fails or length is wrong
            $decoded = base64_decode($key, true);
            return ($decoded !== false && strlen($decoded) === 32) ? $decoded : $key;
        })(),
        'database' => env('DB_DATABASE', 'laravel'),
        'timeout' => env('DB_REMOTE_TIMEOUT', 30),
        'verify_ssl' => env('DB_REMOTE_VERIFY_SSL', true),
        'retry_attempts' => env('DB_REMOTE_RETRY_ATTEMPTS', 3),
        'enable_batching' => env('DB_REMOTE_ENABLE_BATCHING', true),
        'enable_caching' => env('DB_REMOTE_ENABLE_CACHING', true),
        'cache_ttl' => env('DB_REMOTE_CACHE_TTL', 60),
        'prefix' => '',
        'prefix_indexes' => true,
    ],
],

Note:

  • The encryption_key configuration safely handles both base64-encoded and raw 32-byte keys automatically.
  • It prevents PHP 8.1+ deprecation warnings when DB_REMOTE_ENCRYPTION_KEY is not set (e.g., on Server 1 which doesn't need this connection).
  • The connection will only be used when DB_REMOTE_ENDPOINT is configured in your .env file.
  1. Configure your .env file:
# Use remote-http as the default connection
DB_CONNECTION=remote-http

# Remote HTTP Database Configuration
DB_REMOTE_ENDPOINT=http://server1.example.com/remote-db-endpoint
DB_REMOTE_API_KEY=your-generated-api-key-here
DB_REMOTE_ENCRYPTION_KEY=your-generated-encryption-key-here
DB_DATABASE=your_database_name
DB_REMOTE_TIMEOUT=30
DB_REMOTE_VERIFY_SSL=true
DB_REMOTE_RETRY_ATTEMPTS=3

# Performance Optimization (optional)
DB_REMOTE_ENABLE_BATCHING=true
DB_REMOTE_ENABLE_CACHING=true
DB_REMOTE_CACHE_TTL=60

Critical: The DB_REMOTE_API_KEY and DB_REMOTE_ENCRYPTION_KEY must exactly match the values on Server 1.

  1. For local development, you can disable SSL verification:
DB_REMOTE_VERIFY_SSL=false

⚠️ Never disable SSL verification in production!

Step 5: Test the Connection

Test the connection from Server 2:

php artisan tinker
DB::connection()->select('SELECT 1 as test');

Or create a test script:

<?php
require __DIR__.'/vendor/autoload.php';

$app = require_once __DIR__.'/bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();

try {
    $result = DB::connection()->select('SELECT 1 as test');
    echo "βœ“ Connection successful!\n";
} catch (\Exception $e) {
    echo "βœ— Connection failed: " . $e->getMessage() . "\n";
}

That's it! Your Laravel application will now use the remote MySQL database via HTTP.

Usage

Once configured, use your Laravel application exactly as you would with a normal MySQL connection:

// Eloquent models work normally
$user = User::find(1);

// Query builder works normally
$users = DB::table('users')->where('active', 1)->get();

// Transactions work normally
DB::transaction(function () {
    User::create(['name' => 'John']);
    Post::create(['title' => 'Hello']);
});

// Migrations work normally
php artisan migrate

Security Considerations

  1. HTTPS: Always use HTTPS for the remote endpoint URL. Never use HTTP in production.

  2. Strong Keys:

    • Use a strong, random API key (at least 32 characters)
    • Use a strong, random 32-byte encryption key
    • Never commit these keys to version control
  3. IP Whitelisting: Use the REMOTE_DB_ALLOWED_IPS environment variable to restrict access to specific IP addresses. This provides an additional layer of security by rejecting requests from unauthorized IPs early in the request lifecycle. See the IP Whitelisting section for detailed instructions.

  4. Firewall: Consider restricting access to the endpoint via firewall rules (IP whitelist).

  5. Rate Limiting: Consider implementing rate limiting on the remote endpoint to prevent abuse.

  6. Monitoring: Monitor endpoint logs for suspicious activity.

Configuration Options

Client Configuration (Server 2)

Option Environment Variable Description Default
endpoint DB_REMOTE_ENDPOINT The URL of the remote endpoint Required
api_key DB_REMOTE_API_KEY API key for authentication (must match Server 1) Required
encryption_key DB_REMOTE_ENCRYPTION_KEY 32-byte encryption key (can be base64 encoded) Required
database DB_DATABASE Database name laravel
timeout DB_REMOTE_TIMEOUT HTTP request timeout in seconds 30
verify_ssl DB_REMOTE_VERIFY_SSL Whether to verify SSL certificates true
retry_attempts DB_REMOTE_RETRY_ATTEMPTS Number of retry attempts on failure 3
enable_batching DB_REMOTE_ENABLE_BATCHING Enable query batching to reduce HTTP requests true
enable_caching DB_REMOTE_ENABLE_CACHING Enable query result caching true
cache_ttl DB_REMOTE_CACHE_TTL Cache TTL in seconds for SELECT queries 60

Server Configuration (Server 1)

Option Environment Variable Description
endpoint_api_key REMOTE_DB_API_KEY API key for endpoint authentication
endpoint_encryption_key REMOTE_DB_ENCRYPTION_KEY 32-byte encryption key (can be base64 encoded)
endpoint_path REMOTE_DB_ENDPOINT_PATH Custom endpoint path (optional)
endpoint_allowed_ips REMOTE_DB_ALLOWED_IPS Comma-separated list of allowed IP addresses (optional)

How It Works

  1. Query Execution: When Laravel executes a database query, the RemoteHttpConnection intercepts it.

  2. Caching Check: If caching is enabled, the connection first checks if the query result is cached. If found and not expired, it returns the cached result immediately without making an HTTP request.

  3. Query Batching: If batching is enabled, multiple queries can be queued and sent together in a single HTTP request, reducing network overhead.

  4. Encryption: The query and bindings are encrypted using AES-256-GCM with a unique IV for each request.

  5. HTTP Request: The encrypted payload is sent to the remote endpoint via HTTPS POST with the API key in headers.

  6. Remote Processing: The endpoint on Server 1:

    • Validates IP address (if whitelisting is enabled)
    • Validates the API key
    • Decrypts the payload
    • Executes the query/queries on MySQL
    • Maintains transaction state per session
    • Encrypts the response
  7. Response: The encrypted response is sent back, decrypted, and returned to Laravel as if it came from a local database.

  8. Cache Storage: For SELECT queries, the result is cached (if caching is enabled) to avoid redundant requests for the same query.

Performance Optimization

This package includes built-in performance optimizations to reduce HTTP requests and improve page load times:

Query Caching

Query result caching stores SELECT query results in memory to avoid redundant HTTP requests. This is especially useful for:

  • Frequently accessed data
  • Dashboard queries
  • Navigation menus
  • Configuration lookups

Configuration:

DB_REMOTE_ENABLE_CACHING=true
DB_REMOTE_CACHE_TTL=60  # Cache for 60 seconds

The cache is automatically invalidated when write operations (INSERT, UPDATE, DELETE) are performed.

Query Batching

Query batching allows multiple queries to be sent in a single HTTP request, reducing network round-trips. This is particularly beneficial when:

  • Loading related data (e.g., user with posts)
  • Processing multiple records
  • Executing multiple queries in a single request

Configuration:

DB_REMOTE_ENABLE_BATCHING=true

Note: Batching is automatically disabled during transactions to ensure data consistency.

Performance Tips

  1. Enable Caching: For read-heavy applications, enable caching with an appropriate TTL:

    DB_REMOTE_ENABLE_CACHING=true
    DB_REMOTE_CACHE_TTL=300  # 5 minutes for relatively static data
  2. Use Eager Loading: When using Eloquent, use eager loading to reduce the number of queries:

    // Instead of N+1 queries
    $users = User::all();
    foreach ($users as $user) {
        $user->posts; // N queries
    }
    
    // Use eager loading
    $users = User::with('posts')->get(); // 2 queries total
  3. Optimize Queries: Review your application's queries and identify:

    • Duplicate queries that can benefit from caching
    • Queries that can be combined
    • Unnecessary queries that can be removed
  4. Monitor Cache Hit Rate: Consider adding logging to monitor cache effectiveness in production.

IP Whitelisting

The package supports optional IP address whitelisting to restrict access to the remote database endpoint. When enabled, only requests from specified IP addresses will be allowed.

How to Enable IP Whitelisting

  1. Add IP addresses to your .env file on Server 1:
# Comma-separated list of allowed IP addresses
REMOTE_DB_ALLOWED_IPS=192.168.1.100,10.0.0.50,203.0.113.45
  1. Clear the configuration cache:
php artisan config:clear
  1. Restart your application to apply the changes.

How It Works

  • IP validation occurs early in the request lifecycle, before any decryption or database operations
  • Requests from non-whitelisted IPs receive a 403 Forbidden response immediately
  • The package automatically handles proxy headers (X-Forwarded-For, X-Real-IP) to correctly identify the client IP when behind load balancers or reverse proxies
  • If REMOTE_DB_ALLOWED_IPS is not set or empty, all IP addresses are allowed (default behavior)

Examples

Single IP address:

REMOTE_DB_ALLOWED_IPS=192.168.1.100

Multiple IP addresses:

REMOTE_DB_ALLOWED_IPS=192.168.1.100,10.0.0.50,203.0.113.45

Behind a proxy/load balancer: The package automatically detects the real client IP from X-Forwarded-For or X-Real-IP headers. Just whitelist the actual client IPs:

REMOTE_DB_ALLOWED_IPS=203.0.113.45,198.51.100.10

Important Notes

  • IP addresses must match exactly - the package performs exact string matching
  • No CIDR notation support - you must list each IP address individually
  • Proxy-aware - the package correctly identifies client IPs even when behind proxies
  • Optional feature - if not set, all IPs are allowed (backward compatible)

Transaction Handling

Transactions are handled using session-based state management:

  • Each connection gets a unique session ID
  • The remote endpoint maintains transaction state per session
  • BEGIN/COMMIT/ROLLBACK commands are sent as special transaction queries
  • Nested transactions are supported via savepoints

Troubleshooting

"401 Unauthorized" Error

This means the API key doesn't match between servers:

  1. Verify the API keys match exactly on both Server 1 and Server 2:

    • Server 1: REMOTE_DB_API_KEY in .env
    • Server 2: DB_REMOTE_API_KEY in .env
  2. Clear config cache on both servers:

    php artisan config:clear
  3. Restart your application after changing environment variables

"Encryption key must be exactly 32 bytes"

The encryption key must be exactly 32 bytes when decoded. You can:

  1. Use a base64-encoded key (recommended - 44 characters):

    php -r "echo base64_encode(random_bytes(32));"

    Store this in your .env file as-is.

  2. Use a raw 32-byte key (32 characters):

    php -r "echo bin2hex(random_bytes(16));"

    This generates 32 hex characters = 32 bytes.

  3. Verify the key matches on both servers exactly (including any base64 encoding)

"HTTP request failed"

  • Check that the endpoint URL is correct and accessible
  • Verify the endpoint is reachable: curl http://server1.example.com/remote-db-endpoint
  • Check firewall rules on Server 1
  • Verify the API key matches on both servers
  • For local development, ensure DB_REMOTE_VERIFY_SSL=false if using HTTP

"Decryption failed"

  • Ensure the encryption key is identical on both Server 1 and Server 2
  • If using base64 encoding, ensure both servers use the same format
  • Clear config cache: php artisan config:clear

"QueryException: Argument #3 ($bindings) must be of type array"

This error indicates you're using Laravel 12. Make sure you have the latest version of this package that supports Laravel 12's QueryException constructor signature.

Transactions not working

  • Ensure session files can be written on Server 1 (check temp directory permissions)
  • Check that the session ID is being maintained between requests
  • Verify transaction state is being preserved in the session storage

Endpoint not found (404)

  • Ensure REMOTE_DB_API_KEY is set in Server 1's .env file
  • The route is only registered when the API key is present
  • Clear config cache: php artisan config:clear

"403 Forbidden: IP address not allowed"

This error occurs when IP whitelisting is enabled and the request is coming from an unauthorized IP address.

  1. Check the client IP address:

    • Verify the IP address of Server 2 (the client making requests)
    • If behind a proxy/load balancer, check the X-Forwarded-For or X-Real-IP headers
  2. Add the IP to the whitelist on Server 1:

    REMOTE_DB_ALLOWED_IPS=192.168.1.100,10.0.0.50

    Add the client's IP address to the comma-separated list.

  3. Clear config cache on Server 1:

    php artisan config:clear
  4. Temporarily disable IP whitelisting (for testing):

    • Remove or comment out REMOTE_DB_ALLOWED_IPS in Server 1's .env file
    • Clear config cache and restart the application
  5. Verify proxy configuration:

    • If Server 1 is behind a proxy/load balancer, ensure it's properly forwarding client IP headers
    • The package checks X-Forwarded-For and X-Real-IP headers automatically

Testing

Local Development Setup

For local development, you can run both servers on different ports:

Terminal 1 - Server 1 (MySQL Server):

# In your Server 1 project directory
php artisan serve --port=8000

Terminal 2 - Server 2 (Client):

# In your Server 2 project directory
php artisan serve --port=8001

Make sure:

  • Server 1 has MySQL running and REMOTE_DB_API_KEY set
  • Server 2 has DB_REMOTE_ENDPOINT=http://localhost:8000/remote-db-endpoint
  • Both have matching API keys and encryption keys

Running Tests

The package includes tests that can be run with:

composer test

Or using PHPUnit directly:

vendor/bin/phpunit

License

This package is open-sourced software licensed under the MIT license.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please open an issue on GitHub.