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
Requires
- php: >=7.4
- guzzlehttp/guzzle: ^7.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^9.0|^10.0
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
- Install on both servers:
composer require webmavens/laravel-remote-http-database - 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;"
- Server 1 (has MySQL): Set
REMOTE_DB_API_KEYandREMOTE_DB_ENCRYPTION_KEYin.env(optionally addREMOTE_DB_ALLOWED_IPSfor IP whitelisting) - Server 2 (client):
- β οΈ REQUIRED: Manually add the
remote-httpconnection toconfig/database.php(see Step 4 below for the exact code) - Set
DB_CONNECTION=remote-http,DB_REMOTE_ENDPOINT,DB_REMOTE_API_KEY, andDB_REMOTE_ENCRYPTION_KEYin.env
- β οΈ REQUIRED: Manually add the
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.
- Configure your
.envfile 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_KEYcan be any secure string (recommended: 64+ characters) - The
REMOTE_DB_ENCRYPTION_KEYmust 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_IPSis optional. If set, only requests from the specified IP addresses will be allowed. Requests from other IPs will be rejected with a403 Forbiddenresponse before any processing occurs. This provides an additional security layer.
-
The endpoint route is automatically registered at
/remote-db-endpointwhenREMOTE_DB_API_KEYis set. -
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.
- 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_keyconfiguration safely handles both base64-encoded and raw 32-byte keys automatically. - It prevents PHP 8.1+ deprecation warnings when
DB_REMOTE_ENCRYPTION_KEYis not set (e.g., on Server 1 which doesn't need this connection). - The connection will only be used when
DB_REMOTE_ENDPOINTis configured in your.envfile.
- Configure your
.envfile:
# 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.
- 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
-
HTTPS: Always use HTTPS for the remote endpoint URL. Never use HTTP in production.
-
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
-
IP Whitelisting: Use the
REMOTE_DB_ALLOWED_IPSenvironment 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. -
Firewall: Consider restricting access to the endpoint via firewall rules (IP whitelist).
-
Rate Limiting: Consider implementing rate limiting on the remote endpoint to prevent abuse.
-
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
-
Query Execution: When Laravel executes a database query, the
RemoteHttpConnectionintercepts it. -
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.
-
Query Batching: If batching is enabled, multiple queries can be queued and sent together in a single HTTP request, reducing network overhead.
-
Encryption: The query and bindings are encrypted using AES-256-GCM with a unique IV for each request.
-
HTTP Request: The encrypted payload is sent to the remote endpoint via HTTPS POST with the API key in headers.
-
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
-
Response: The encrypted response is sent back, decrypted, and returned to Laravel as if it came from a local database.
-
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
-
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
-
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
-
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
-
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
- Add IP addresses to your
.envfile 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
- Clear the configuration cache:
php artisan config:clear
- 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 Forbiddenresponse 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_IPSis 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:
-
Verify the API keys match exactly on both Server 1 and Server 2:
- Server 1:
REMOTE_DB_API_KEYin.env - Server 2:
DB_REMOTE_API_KEYin.env
- Server 1:
-
Clear config cache on both servers:
php artisan config:clear
-
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:
-
Use a base64-encoded key (recommended - 44 characters):
php -r "echo base64_encode(random_bytes(32));"Store this in your
.envfile as-is. -
Use a raw 32-byte key (32 characters):
php -r "echo bin2hex(random_bytes(16));"This generates 32 hex characters = 32 bytes.
-
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=falseif 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_KEYis set in Server 1's.envfile - 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.
-
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-FororX-Real-IPheaders
-
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.
-
Clear config cache on Server 1:
php artisan config:clear
-
Temporarily disable IP whitelisting (for testing):
- Remove or comment out
REMOTE_DB_ALLOWED_IPSin Server 1's.envfile - Clear config cache and restart the application
- Remove or comment out
-
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-ForandX-Real-IPheaders 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_KEYset - 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.