artisan command line tool. In this guide, you point a new Laravel app at ClickHouse Managed Postgres, create a tasks table with a migration, and serve its rows from a route. Every connection uses TLS with full certificate verification.
Prerequisites
- PHP 8.3 or later, as required by Laravel 13, with the
pdo_pgsqlextension. Runphp -mand check thatpdo_pgsqlis in the list. - Composer
- A ClickHouse Cloud account
psql, to create the database. You can also run theCREATE DATABASEstatement in the SQL console.
Create a ClickHouse Managed Postgres service
In the ClickHouse Cloud console, click New service and select Postgres. The instance is ready in a few minutes. See the quickstart for a walkthrough.Get your connection details
Click Connect in the left sidebar of your service. The modal shows your username, password, server, and port, and it has a Directly / via PgBouncer toggle. A PHP app doesn’t keep a connection pool between requests. By default, every request opens a new Postgres connection and closes it when the request ends. This guide connects the app through the bundled PgBouncer on port6432, so those short-lived connections share a small set of Postgres backends. Migrations connect directly to Postgres on port 5432, because they run DDL and can hold long transactions.
Select via PgBouncer to see the pooled connection details. The port changes to 6432, and the connection strings include sslmode=verify-full.
In the same modal, click Download CA certificate. You can also download it from Settings → CA Certificate. The certificate is unique to your instance, so the driver can use it to verify that it’s talking to your server.
Set up the project
Create a Laravel project:ca-certificate.pem.
Create a database for the app. Replace <PASSWORD> and the host with the values from the Connect modal:
Configure the connection
Laravel uses SQLite by default. Open.env and replace the DB_CONNECTION=sqlite line and the commented-out DB_ lines below it with:
.env
ca-certificate.pem. libpq resolves a relative path against the working directory of the PHP process, which depends on how PHP runs your app. If your password contains spaces or #, wrap it in double quotes. Keep .env out of version control.
The default pgsql connection in config/database.php reads DB_SSLMODE, but not the CA certificate path. Open config/database.php and add the sslrootcert and options entries to the pgsql connection:
config/database.php
sslmode and sslrootcert to the PDO DSN, and the pdo_pgsql extension passes them to libpq, which checks the certificate chain and the hostname.
PDO::ATTR_EMULATE_PREPARES makes PDO send each query with its values already bound instead of creating a server-side prepared statement. Set it when you connect through PgBouncer. When pdo_pgsql is built against libpq 16 or older, for example on Debian 12, PHP frees each prepared statement with a SQL DEALLOCATE statement. PgBouncer renames prepared statements on the server, so the DEALLOCATE fails. Inside a transaction, that aborts the transaction, and the next query fails with current transaction is aborted. With libpq 17 or later, the default settings also work. Run php -i | grep libpq to see your version.
Check the connection:
Without the
sslrootcert line, verify-full fails with root certificate file ".../.postgresql/root.crt" does not exist, because libpq falls back to its default CA location. With the wrong CA, the connection fails with SSL error: certificate verify failed.Create a model and migration
Generate an Eloquent model and a migration for atasks table:
database/migrations/ (its name ends in _create_tasks_table.php) and add the title and done columns to up:
database/migrations/xxxx_xx_xx_xxxxxx_create_tasks_table.php
app/Models/Task.php so that the columns can be mass-assigned and done is returned as a boolean:
app/Models/Task.php
.env, so you only need to change the port:
tasks, this creates the tables for the users, sessions, cache, and jobs that a new Laravel app uses by default. Laravel records applied migrations in the migrations table and only runs new ones on later calls.
Query the database
Add a few rows with Eloquent fromartisan tinker:
routes/web.php:
routes/web.php
Laravel applies the
search_path, timezone, and isolation_level connection options with session-level SET statements when it connects, and those don’t carry over between transactions through PgBouncer. Keep these options at their defaults, or connect directly on port 5432 if you need them. Session-level features such as LISTEN and session advisory locks also don’t work through PgBouncer. See the FAQ.Run and verify
Start the development server:http://localhost:3104/tasks in your browser. The response looks like this:
guide_laravel and then public, and click the tasks table. The public schema also lists the default Laravel tables, such as users, sessions, and migrations:
Next steps
- Connection: connection strings, PgBouncer, and TLS
- Settings: Postgres and PgBouncer parameters, such as
max_connections - Read replicas: send reads to a replica with Laravel’s read and write connections
- Security: IP access lists and private networking