How to fix "SQLSTATE[HY000] [2002] Connection refused" Laravel error in GitHub Actions

Last updated on by

How to fix "SQLSTATE[HY000] [2002] Connection refused" Laravel error in GitHub Actions image

GitHub Actions is a popular tool for setting up continuous integration (CI) workflows for your repositories hosted on GitHub. It allows you to define workflows in YAML files that can be used to automate tasks such as running tests, static analysis, and deployments.

A common error you may come across when setting up your GitHub Actions CI workflow for a Laravel application which uses MySQL is "SQLSTATE[HY000] [2002] Connection refused". This error occurs when your application cannot connect to the MySQL database. You'll typically encounter this when running migrations at the start of your tests. When you inspect your workflow's output on GitHub, you might see a message that looks like this:

SQLSTATE[HY000] [2002] Connection refused (Connection: mysql, SQL: select exists (select 1 from information_schema.tables where table_schema = 'testing' and table_name = 'migrations' and table_type in ('BASE TABLE', 'SYSTEM VERSIONED')) as `exists`)
at vendor/laravel/framework/src/Illuminate/Database/Connection.php:825
821▕ $this->getName(), $query, $this->prepareBindings($bindings), $e
822▕ );
823▕ }
824▕
➜ 825▕ throw new QueryException(
826▕ $this->getName(), $query, $this->prepareBindings($bindings), $e
827▕ );
828▕ }
829▕ }

As we can see, the reason for this error is not immediately obvious. We're just told that the connection is being refused.

In this article, we'll take a look at three common reasons you might encounter this error in your GitHub Actions workflow and how to fix them.

Valid GitHub Actions Workflow

Before we look at some of the possible causes of the "SQLSTATE[HY000] [2002] Connection refused" error, let's first look at a basic valid GitHub Actions workflow for a Laravel application that uses MySQL.

We'll keep referring to this example throughout the article to demonstrate the issues and how to fix them.

The following is a GitHub Action workflow that runs tests for a Laravel application using MySQL:

name: Run tests
 
on: [push]
 
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
 
# Configure the MySQL service
services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
 
steps:
# Checkout the repository
- uses: actions/checkout@v4
with:
fetch-depth: 1
 
# Setup PHP 8.3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
 
# Install the Composer dependencies
- name: Run composer install
run: composer install -n --prefer-dist
 
# Copy the .env.ci file to .env and generate the application key
- name: Prepare Laravel Application
run: |
cp .env.ci .env
php artisan key:generate
 
# Run the tests
- name: Run tests
run: php artisan test

In the workflow above, we're creating a job called tests that runs on every push to the repository. The job runs on PHP 8.3, MySQL 8, and the latest version of Ubuntu. Before we run the tests, we install the Composer dependencies, copy the .env.ci file to .env, generate the application key, and then run the tests.

For this article, we'll assume the .env.ci file contains the correct environment variables needed to run the workflow on GitHub Actions. The only ones we're interested in are the database connection details that have been set up to connect to the MySQL service:

DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=root
DB_PASSWORD=password

Now, let's move on to the common causes of the "SQLSTATE[HY000] [2002] Connection refused" error and how to fix them.

Using Incorrect MySQL Port

The most common reason I run into the "SQLSTATE[HY000] [2002] Connection refused" error when connecting to MySQL in GitHub Actions is using an incorrect port number.

Dynamic Port Number

By default, MySQL runs on port 3306. However, when running it within a service container inside a GitHub Actions workflow, a random port number on the workflow runner will be assigned to the service container's 3306 port. This means you never know which port number to use to connect to MySQL.

For example, let's take this workflow file that instructs GitHub Actions to expose port 3306 on the MySQL service:

name: Run tests
 
on: [push]
 
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
 
services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
 
steps:
# ...
 
- name: Run tests
run: php artisan test

We're exposing port 3306 on the MySQL service container and allowing GitHub Actions to map it to a random port on the workflow runner. This means we can't rely on port number 3306 to connect to MySQL.

If we were to try running our workflow, we'd encounter the "SQLSTATE[HY000] [2002] Connection refused" error because we're trying to connect to MySQL using port 3306 in our .env.ci file rather than the port number assigned by GitHub Actions.

However, we can read the port number that GitHub Actions assigns to the 3306 port of the service container using: ${{ job.services.mysql.ports['3306'] }}. We can use this to override our DB_PORT environment variable with this value by updating our "Run tests" step like so:

- name: Run tests
run: php artisan test
env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }}

Let's say that the workflow runner was assigned port 1234 for accessing port 3306 of the MySQL container. This would result in the DB_PORT environment variable being set to 1234.

Hardcoded Port Number

If you'd prefer to explicitly set the port number that should be used to access the MySQL service, you can do this in the workflow file. This is useful if you want to avoid the random port number assignment by GitHub Actions.

In our valid example, we map port 3306 on the MySQL service to port 3306 on the workflow runner using the syntax:WORKFLOW_RUNNER_PORT:SERVICE_PORT. In this case, we're using 3306:3306 like so:

services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

As a result, we can access the MySQL service using the existing values set in our .env.ci file since they're set to use port 3306.

However, there may be times when you'd prefer to use a different port number. You might want to do this if you have clashes with other services running on the same port, such as another MySQL service. In this case, you can change the port number for the MySQL service in the workflow file. For example, let's change the port number to 33306:

services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 33306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

Now, to access MySQL, you can either update the .env.ci file to use the new port number:

DB_HOST=127.0.0.1
DB_PORT=33306
DB_DATABASE=testing
DB_USERNAME=root
DB_PASSWORD=password

Or, you can specify the environment variable in the workflow file, as we showed earlier:

- name: Run tests
run: php artisan test
env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }}

No MySQL Service Defined in the Workflow

Another reason you might encounter the error is if you don't have a MySQL service defined in your workflow. This is an easy mistake to make, especially if you're new to GitHub Actions or you've copied a workflow from somewhere and forgot to include the MySQL service.

Let's take a look at what our workflow would look like without the MySQL service:

name: Run tests
 
on: [push]
 
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
 
# ⚠️ Missing MySQL service here! ⚠️
 
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
 
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
 
- name: Run composer install
run: composer install -n --prefer-dist
 
- name: Prepare Laravel Application
run: |
cp .env.ci .env
php artisan key:generate
 
- name: Run tests
run: php artisan test

As we can see, if I hadn't added the warning message in the example above, it might not be immediately obvious that the MySQL service is missing. But if you run this workflow, you'll see the "SQLSTATE[HY000] [2002] Connection refused" error.

To fix this, we can add a MySQL service by defining one like in our valid example:

name: Run tests
 
on: [push]
 
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
 
# Configure the MySQL service
services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
 
steps:
# ...

MySQL Is Not Available Yet

Another cause for the "SQLSTATE[HY000] [2002] Connection refused" error is that the mysql service container has booted, but MySQL itself is not yet available to accept connections and handle queries.

In our valid workflow example, we can see that we have a command defined in the MySQL service options:

options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

This command checks whether MySQL is ready to accept connections and prevents the workflow from continuing until it is ready to do so. It runs mysqladmin ping every 10 seconds with a timeout of five seconds and retries three times. If one of the checks succeeds, MySQL is ready, and the rest of the workflow will continue. If MySQL is not ready after three retries, the workflow will fail.

If we don't have this check in place, once the mysql service is booted, the workflow will continue to the next step, regardless of whether MySQL is ready to accept connections.

In many cases, MySQL will be ready by the time you run the tests. Other steps tend to be involved before the workflow runner runs the tests, such as setting up PHP, installing Composer dependencies, and preparing the Laravel application. So, this already adds some time before running the tests, and MySQL is typically ready by then. However, this isn't always a guarantee. You might also have other steps in your workflow that need to interact with MySQL early on, which could also cause this issue. So, it's a handy check to have in place.

Without the check in place, your workflow YAML file might look like this:

name: Run tests
 
on: [push]
 
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
 
services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306:3306
 
steps:
# ...

We can add the check to the bottom of the MySQL service definition:

mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

Summary

In this article, we've looked at three common reasons you might encounter the "SQLSTATE[HY000] [2002] Connection refused" error in your GitHub Actions workflow for a Laravel application that uses MySQL.

As we've seen, the error can occur due to using the wrong port number, not having a MySQL service defined in the workflow, or MySQL not being ready to accept connections.

Hopefully, this article has given you some insight into how to fix this error and ensure your GitHub Actions workflow runs smoothly.

As well as encountering issues with MySQL in GitHub Actions, you may encounter pesky bugs with your production applications. For this reason, it's essential to use a good error monitoring tool that can help you quickly identify and fix issues. An excellent tool for this is Honeybadger, which can help you track, prioritize, and fix errors. A bonus of using Honeybadger is that it integrates seamlessly with Laravel, so you can get up and running quickly. I'd highly recommend checking it out so you can have peace of mind that your applications are running smoothly.

Ashley Allen photo

I am a freelance Laravel web developer who loves contributing to open-source projects, building exciting systems, and helping others learn about web development.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
Cut PHP Code Review Time & Bugs into Half with CodeRabbit logo

Cut PHP Code Review Time & Bugs into Half with CodeRabbit

CodeRabbit is an AI-powered code review tool that specializes in PHP and Laravel, running PHPStan and offering automated PR analysis, security checks, and custom review features while remaining free for open-source projects.

Cut PHP Code Review Time & Bugs into Half with CodeRabbit
Join the Mastering Laravel community logo

Join the Mastering Laravel community

Connect with experienced developers in a friendly, noise-free environment. Get insights, share ideas, and find support for your coding challenges. Join us today and elevate your Laravel skills!

Join the Mastering Laravel community
Laravel Idea for PhpStorm logo

Laravel Idea for PhpStorm

Ultimate PhpStorm plugin for Laravel developers, delivering lightning-fast code completion, intelligent navigation, and powerful generation tools to supercharge productivity.

Laravel Idea for PhpStorm
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Handling Request Data Presence in Laravel image

Handling Request Data Presence in Laravel

Read article
First Factor One-Time Passwords for Laravel with OTPZ image

First Factor One-Time Passwords for Laravel with OTPZ

Read article
Managing Request Host Information in Laravel image

Managing Request Host Information in Laravel

Read article
Tim Leland: URL Shorteners, browser extensions, and more image

Tim Leland: URL Shorteners, browser extensions, and more

Read article
HTTP Method Verification in Laravel image

HTTP Method Verification in Laravel

Read article
Packistry is a Self-hosted Composer Repository Made with Laravel image

Packistry is a Self-hosted Composer Repository Made with Laravel

Read article