How to Troubleshoot Database Connection Refused Errors on Fedora
You just finished setting up PostgreSQL or MariaDB on your Fedora machine. You run your application, and it immediately crashes with Connection refused: connect. You check the service, it says active. You check the firewall, it looks fine. You are staring at a terminal that refuses to tell you why the database is ignoring you. This happens when the network stack, the service configuration, and the security policies disagree on where the database should be listening.
What is actually happening
A "connection refused" error is a specific network signal. It means your client reached the correct IP address, but the operating system actively rejected the connection attempt on that port. It is not a timeout. A timeout means the packet vanished into the void or was silently dropped. A refusal means the host answered, "I am here, but nobody is listening on that door."
Think of it like knocking on a house. A timeout is a dead phone line. A connection refused is a person answering the door and saying, "No one lives here," even though you see lights on inside. The database process might be running, but it is bound to the wrong interface, blocked by a firewall rule, or stopped by SELinux before it could open the socket. The error comes from the kernel, not the database. The database never sees the request.
Verify the service is running
Start by confirming the database process is actually running and hasn't crashed silently. Fedora uses systemd to manage services. The status command shows the current state and recent log lines in one view. Always check status before restarting. A restart just repeats the same failure if the root cause is a config error.
# Check the service state and recent log lines in one view
systemctl status postgresql.service
# WHY: status shows active/inactive state AND the last 10 log lines
# WHY: This catches immediate crashes that 'is-active' would miss
# WHY: The log lines often contain the exact reason for failure
If the output shows Active: inactive (dead), the service failed to start. Look at the log lines below the status header. You will often see Error: could not bind to address or Permission denied. Fix the underlying config error before trying to start the service again. Forcing a start with a broken config just generates more noise.
If the service is active, move to the next step. The process is running, but it might not be listening where you expect.
Confirm the port and interface binding
Databases bind to specific IP addresses and ports. If the database binds to 127.0.0.1 and you try to connect from a remote machine, the kernel will refuse the connection. If it binds to a port other than the default, your client will hit the wrong door.
# List all listening TCP sockets with process names
ss -tlnp | grep -E '5432|3306'
# WHY: -t shows TCP, -l shows listening, -n disables name resolution
# WHY: -p shows the process name so you can confirm it is the DB
# WHY: grep filters for PostgreSQL 5432 or MariaDB 3306
If the port does not appear, the daemon is either not running or bound to a different address. If 127.0.0.1 appears but you need remote access, the binding is too restrictive. Update the configuration to bind to the correct address. Always edit files in /etc/. Files in /usr/lib/ ship with the package and get overwritten on updates.
# /var/lib/pgsql/data/postgresql.conf
# Change localhost to all interfaces or a specific IP
listen_addresses = '*'
# WHY: '*' binds to all network interfaces
# WHY: Use 'localhost' if you only need local access
# WHY: This setting requires a service restart to take effect
For MariaDB, Fedora uses a drop-in directory structure. The main config is /etc/my.cnf. Specific settings go in /etc/my.cnf.d/. Do not edit /etc/my.cnf directly if you can avoid it. Create a file in the drop-in directory for your custom settings. This keeps your changes separate from package defaults.
# /etc/my.cnf.d/custom.cnf
[mysqld]
bind-address = 0.0.0.0
# WHY: 0.0.0.0 binds to all IPv4 interfaces
# WHY: Drop-in files are merged with the main config
# WHY: This structure prevents config drift during updates
Restart the service after config changes. The config file is not hot-reloaded for binding addresses. The process must rebind the sockets.
Unix sockets versus TCP connections
Databases often prefer Unix domain sockets for local connections. These are faster and bypass the network stack entirely. A "connection refused" error on TCP might occur while the socket connection works perfectly. If your application connects via localhost, some drivers try TCP, others try sockets. If the database is configured to reject TCP connections, the driver might fail.
Check your application's connection string. Use host=127.0.0.1 to force TCP. Use host=/var/run/postgresql to force a socket. Mismatched expectations between the driver and the server configuration are a common source of confusion.
# Check for Unix sockets in the run directory
ls -l /var/run/postgresql/
# WHY: PostgreSQL creates socket files here for local IPC
# WHY: Socket files look like directories but are special files
# WHY: Applications can connect via path instead of IP
Check the firewall
Fedora's firewall blocks incoming connections by default. Open the port for the database service. The firewall has a runtime configuration and a persistent configuration. Changes to the persistent config do not apply until you reload.
# Add the service to the permanent firewall configuration
sudo firewall-cmd --permanent --add-service=postgresql
# WHY: --permanent writes to the config file for persistence across reboots
# WHY: Without this flag, the rule vanishes after a restart
sudo firewall-cmd --reload
# WHY: --reload applies the permanent rules to the running firewall
# WHY: Runtime and persistent configs diverge if you skip this step
Run firewall-cmd --reload after every rule change. Otherwise the runtime config and the persistent config diverge. You might see the rule in --list-all but the connection still fails because the runtime hasn't updated.
Check SELinux
Fedora enforces SELinux by default. A database attempting to listen on a non-standard port or connect to the network may be blocked. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. The summary usually tells you exactly what label is missing.
# Check for recent SELinux denials related to the database
sudo ausearch -m AVC -ts recent | grep -i postgresql
# WHY: -m AVC filters for Access Vector Cache denials
# WHY: -ts recent limits output to the last few minutes
# WHY: grep isolates the specific service from system noise
If you see denials about port access, you need to add the port to the SELinux policy. SELinux blocks ports not explicitly allowed by type.
# Allow PostgreSQL to listen on a custom port
sudo semanage port -a -t postgresql_port_t -p tcp 5433
# WHY: -a adds a new port definition to the SELinux policy
# WHY: -t assigns the postgresql_port_t type to the port
# WHY: SELinux blocks ports not explicitly allowed by type
Read the SELinux denial summary before disabling SELinux. Disabling SELinux hides the problem and exposes the system to other risks. Fix the policy instead.
Review logs for context
When the service is running but connections fail, the logs often contain the reason. The database might be rejecting connections due to authentication settings, max connections, or resource limits. Use journalctl with the -xe flags. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type journalctl -xeu <unit> muscle-memory style.
# View detailed logs for the database service
sudo journalctl -u postgresql.service -xe
# WHY: -u filters logs for the specific unit
# WHY: -x adds explanatory text from man pages
# WHY: -e jumps to the end of the log buffer
Look for FATAL: password authentication failed. This error means the connection succeeded and the database rejected the credentials. This is not a "connection refused" error. Fix the network path first. Authentication errors come later.
Verify the fix
Test the connection from the client perspective to confirm the fix. Use a simple tool to test raw TCP connectivity without involving database credentials. This isolates network issues from application issues.
# Test TCP connectivity to the database port
nc -zv 127.0.0.1 5432
# WHY: -z enables zero-I/O mode for scanning ports
# WHY: -v prints verbose output showing success or failure
# WHY: This tests the network path without needing DB credentials
If you see nc: connect to 127.0.0.1 port 5432 (tcp) failed: Connection refused, the database is still not listening. If you see succeeded!, the network path is open. The issue is likely in the database configuration or the application connection string.
Test with nc -zv before running the application. Network failures are faster to debug than application stack traces.
Common pitfalls
The pg_hba.conf file controls authentication, not connectivity. A "connection refused" error happens before authentication. If you see FATAL: password authentication failed, the connection succeeded and the database rejected the credentials. Fix the network path first. Authentication errors come later.
MariaDB and MySQL use different service names. Fedora uses mariadb.service. Running systemctl status mysql will fail because the unit does not exist. Use the correct unit name for the package you installed.
IPv6 binding can cause confusion. If the database binds to ::1 and the client connects to 127.0.0.1, the connection might fail depending on the client's socket resolution. Ensure both sides agree on the address family. Some clients prefer IPv6 and will fail if the server only listens on IPv4.
When to use this approach
Use systemctl status when you need to see the service state and recent logs in one view. Use ss -tlnp when you need to verify which process is bound to a specific port and interface. Use firewall-cmd --permanent when you need to open a port that survives a system reboot. Use semanage port when the database is running but SELinux blocks access on a non-standard port. Use nc -zv when you want to test raw TCP connectivity without involving database credentials. Use journalctl -u postgresql -xe when the service crashes and you need the full context of the failure.