By default, the Docker daemon socket at /var/run/docker.sock is accessible only on the local host, but when you need to manage containers remotely — from a CI system, a monitoring server, or a developer workstation — you must expose the Docker API over TCP. Without TLS, any client with network access can control your Docker daemon and, by extension, the entire host. This tutorial walks through generating a full certificate authority, server and client certificates, and configuring the Docker daemon on RHEL 8 to require mutual TLS authentication on port 2376.
Prerequisites
- RHEL 8 server with Docker CE installed and running
- The
opensslpackage installed:dnf install -y openssl - The server’s hostname or IP address that clients will connect to (e.g.,
docker.example.com/192.168.1.10) - Port 2376 open in the firewall:
firewall-cmd --add-port=2376/tcp --permanent && firewall-cmd --reload - Root access on the Docker host
Step 1 — Generate the Certificate Authority
All certificates in this chain will be signed by a private CA that you control. Generate the CA key and self-signed certificate first. Store these in a secure location — anyone with the CA key can issue trusted certificates.
mkdir -p /etc/docker/tls && cd /etc/docker/tls
# Generate CA private key (4096-bit RSA)
openssl genrsa -aes256 -out ca-key.pem 4096
# Generate the self-signed CA certificate (valid 3650 days)
openssl req -new -x509 -days 3650
-key ca-key.pem
-sha256
-out ca.pem
-subj "/C=US/ST=California/L=San Francisco/O=MyOrg/CN=DockerCA"
chmod 0400 ca-key.pem
chmod 0444 ca.pem
Step 2 — Generate the Server Certificate
The server certificate is presented by the Docker daemon to connecting clients. It must include the server’s hostname and IP address as Subject Alternative Names (SANs) so that client TLS verification succeeds.
cd /etc/docker/tls
# Generate server key
openssl genrsa -out server-key.pem 4096
# Create CSR
openssl req -subj "/CN=docker.example.com"
-sha256 -new
-key server-key.pem
-out server.csr
# Add SANs — include all hostnames and IPs clients may use
cat > extfile.cnf <<'EOF'
subjectAltName = DNS:docker.example.com,IP:192.168.1.10,IP:127.0.0.1
extendedKeyUsage = serverAuth
EOF
# Sign the server certificate with the CA
openssl x509 -req -days 730
-sha256
-in server.csr
-CA ca.pem
-CAkey ca-key.pem
-CAcreateserial
-out server-cert.pem
-extfile extfile.cnf
chmod 0400 server-key.pem
chmod 0444 server-cert.pem
Step 3 — Generate Client Certificates
Each client (CI server, developer workstation) should have its own key pair signed by the same CA. The Docker daemon will verify these certificates to authenticate clients, implementing mutual TLS.
cd /etc/docker/tls
# Generate client key
openssl genrsa -out client-key.pem 4096
# Create client CSR
openssl req -subj "/CN=docker-client"
-new -key client-key.pem
-out client.csr
# Client cert extension — clientAuth only
cat > extfile-client.cnf <<'EOF'
extendedKeyUsage = clientAuth
EOF
# Sign with the CA
openssl x509 -req -days 730
-sha256
-in client.csr
-CA ca.pem
-CAkey ca-key.pem
-CAcreateserial
-out client-cert.pem
-extfile extfile-client.cnf
chmod 0400 client-key.pem
chmod 0444 client-cert.pem
# Package client certs for distribution to the client machine
tar czf /tmp/docker-client-tls.tar.gz ca.pem client-cert.pem client-key.pem
Step 4 — Configure the Docker Daemon
Update /etc/docker/daemon.json to enable TLS verification and specify the certificate paths. The hosts array tells the daemon to listen on the TCP socket in addition to (or instead of) the Unix socket.
cat > /etc/docker/daemon.json < /etc/systemd/system/docker.service.d/override.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF
systemctl daemon-reload
systemctl restart docker
systemctl status docker
Step 5 — Connect from a Client Using TLS
On the client machine, copy the ca.pem, client-cert.pem, and client-key.pem files and use them with the Docker CLI. The default client certificate directory is ~/.docker/.
# On the client machine — copy certs
mkdir -p ~/.docker
scp [email protected]:/tmp/docker-client-tls.tar.gz .
tar xzf docker-client-tls.tar.gz -C ~/.docker/
mv ~/.docker/client-cert.pem ~/.docker/cert.pem
mv ~/.docker/client-key.pem ~/.docker/key.pem
# Connect using explicit flags
docker
-H tcp://docker.example.com:2376
--tlsverify
--tlscacert ~/.docker/ca.pem
--tlscert ~/.docker/cert.pem
--tlskey ~/.docker/key.pem
info
# Or set environment variables for the session
export DOCKER_HOST=tcp://docker.example.com:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=~/.docker
docker ps
Step 6 — Verify and Harden
Confirm that the daemon refuses unauthenticated connections and that certificates are working as expected. Never expose the Docker socket (/var/run/docker.sock) over a network proxy without TLS in front of it.
# Confirm unauthenticated connections are rejected
curl http://docker.example.com:2376/info
# Expected: connection closed or TLS handshake error
# Confirm TLS connection works
curl
--cacert ~/.docker/ca.pem
--cert ~/.docker/cert.pem
--key ~/.docker/key.pem
https://docker.example.com:2376/info | python3 -m json.tool | grep Name
# Verify certificate details
openssl s_client
-connect docker.example.com:2376
-CAfile ~/.docker/ca.pem
-cert ~/.docker/cert.pem
-key ~/.docker/key.pem
2>&1 | grep -E 'Verify|subject|issuer'
# Check firewall rule is limited to trusted CIDR ranges
firewall-cmd --list-all
Conclusion
Exposing the Docker daemon over TCP without TLS is equivalent to granting unrestricted root access to any network client — it is one of the most common and severe Docker misconfigurations. By generating a private CA, issuing server and client certificates, and configuring mutual TLS in /etc/docker/daemon.json, you ensure that only clients in possession of a CA-signed certificate can connect to your RHEL 8 Docker daemon. Rotate certificates annually and revoke individual client certificates immediately if a workstation or CI credential is compromised.
Next steps: How to Use Docker Secrets and Environment Variables Securely on RHEL 8, How to Build Docker Images with Multi-Stage Builds on RHEL 8, and How to Set Up a Kubernetes Dashboard on RHEL 8.