Puppet follows a server-agent architecture where you define infrastructure state in declarative manifests, and every managed node enforces that state automatically. Instead of SSHing into dozens of machines to update a config file, you write it once, push it to the Puppet Server, and let every agent pull the change on its next check-in. If someone manually edits a managed file or stops a service, Puppet catches the drift and corrects it within 30 minutes.
This guide walks through installing Puppet 8 Server and Agent on Rocky Linux 10 (also works on AlmaLinux 10 and RHEL 10). Puppet doesn’t ship official EL-10 packages yet, but the EL-9 packages install and run correctly with one small workaround for Java. We cover the full setup: repository installation, server and agent configuration, certificate signing, writing manifests, deploying a real Nginx configuration to a managed node, and testing drift remediation. If you need the same setup on Debian or Ubuntu, check our guide on setting up Puppet Server and Agent on Ubuntu 24.04.
Last verified: March 2026 | Tested on Rocky Linux 10.1 with Puppet Server 8.7.0, Puppet Agent 8.10.0, OpenJDK 21.0.10
Prerequisites
You need two Rocky Linux 10 or AlmaLinux 10 servers. Here are the minimum specs:
- Puppet Server: 2 CPU cores, 4 GB RAM minimum (Puppet Server runs on the JVM and needs at least 2 GB heap)
- Puppet Agent: 1 CPU core, 1 GB RAM
- Root or sudo access on both servers
- Port 8140/tcp open between the two nodes (Puppet’s communication port)
- Hostnames configured and resolvable between servers (DNS or
/etc/hosts) - Tested on: Rocky Linux 10.1, Puppet Server 8.7.0, Puppet Agent 8.10.0, OpenJDK 21.0.10
Here is the lab setup used throughout this guide:
| Role | Hostname | IP Address | OS |
|---|---|---|---|
| Puppet Server | puppet-server.example.com | 10.0.1.10 | Rocky Linux 10 |
| Puppet Agent | puppet-agent.example.com | 10.0.1.11 | Rocky Linux 10 |
Replace the IP addresses with your own. The steps are identical for AlmaLinux 10 and RHEL 10.
Step 1: Configure Hostnames and DNS Resolution
Puppet relies heavily on hostnames for certificate generation and agent-to-server communication. Both servers must resolve each other by FQDN before you install anything.
On the Puppet Server node, set the hostname:
sudo hostnamectl set-hostname puppet-server.example.com
On the Puppet Agent node:
sudo hostnamectl set-hostname puppet-agent.example.com
If you do not have a DNS server handling resolution between the nodes, add entries to /etc/hosts on both servers:
sudo vi /etc/hosts
Add these lines (replace with your actual IPs):
10.0.1.10 puppet-server.example.com puppet-server
10.0.1.11 puppet-agent.example.com puppet-agent
Verify hostname resolution from both servers:
hostname -f
The output should show the full FQDN you just set:
puppet-server.example.com
Test connectivity by pinging each server from the other:
ping -c 3 puppet-server.example.com
ping -c 3 puppet-agent.example.com
Step 2: Install the Puppet 8 Repository
Puppet packages are not in the default Rocky Linux or AlmaLinux repositories. As of March 2026, Puppet Labs has not released official EL-10 packages, but the EL-9 packages install and run correctly on Rocky Linux 10. Install the official Puppet 8 release repository on both the server and the agent:
sudo dnf install -y https://yum.puppet.com/puppet8-release-el-9.noarch.rpm
Confirm the repository is available:
sudo dnf repolist | grep puppet
You should see the puppet8 repository listed:
puppet8 Puppet 8 Repository el 9 - x86_64
Step 3: Install Puppet Agent on Both Servers
Install the puppet-agent package on both servers. This provides the Puppet binary, Facter, and all required Ruby dependencies. The EL-9 agent package installs cleanly on Rocky Linux 10 with no modifications:
sudo dnf install -y puppet-agent
After installation, load the Puppet binaries into your shell PATH:
source /etc/profile.d/puppet-agent.sh
Verify the installed version:
puppet --version
You should see:
8.10.0
Step 4: Install Puppet Server
On the Puppet Server node only, install the puppetserver package. This is where the EL-10 workaround comes in. The puppetserver RPM depends on java-17-openjdk-headless, which is not available on Rocky Linux 10 (EL-10 ships Java 21 and 25 instead). The fix is straightforward: install Java 21, then install the puppetserver RPM while skipping the Java 17 dependency check.
First, install Java 21:
sudo dnf install -y java-21-openjdk-headless
Confirm Java 21 is installed:
java -version
You should see output similar to:
openjdk version "21.0.10" 2026-01-20 LTS
OpenJDK Runtime Environment (Red_Hat-21.0.10.0.7-1) (build 21.0.10+7-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-21.0.10.0.7-1) (build 21.0.10+7-LTS, mixed mode, sharing)
Now download the puppetserver RPM and install it with --nodeps to bypass the Java 17 dependency:
cd /tmp
sudo dnf download puppetserver
Install the downloaded RPM:
sudo rpm -ivh --nodeps /tmp/puppetserver-*.noarch.rpm
rm -f /tmp/puppetserver-*.noarch.rpm
The --nodeps flag is safe here because we already installed a compatible Java version. Puppet Server 8.7.0 works correctly with OpenJDK 21. Verify both components are installed:
source /etc/profile.d/puppet-agent.sh
puppet --version
puppetserver --version
Expected output:
8.10.0
puppetserver version: 8.7.0
Step 5: Configure Puppet Server
Before starting the Puppet Server service, configure the main settings in puppet.conf. This file controls how both the server and agent behave.
Set the server hostname and certificate name:
sudo puppet config set server puppet-server.example.com --section main
sudo puppet config set certname puppet-server.example.com --section main
Set DNS alternative names for the server certificate. This allows agents to connect using any of these names:
sudo puppet config set dns_alt_names 'puppet-server.example.com,puppet-server,puppet' --section server
Verify the configuration:
cat /etc/puppetlabs/puppet/puppet.conf
The relevant sections should look like this:
[main]
server = puppet-server.example.com
certname = puppet-server.example.com
[server]
vardir = /opt/puppetlabs/server/data/puppetserver
logdir = /var/log/puppetlabs/puppetserver
rundir = /var/run/puppetlabs/puppetserver
pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid
codedir = /etc/puppetlabs/code
dns_alt_names = puppet-server.example.com,puppet-server,puppet
Adjust JVM Memory Allocation
Puppet Server allocates 2 GB of heap memory by default. For a small deployment (under 10 agents), you can reduce this to 1 GB. For larger environments, keep the default or increase it. The configuration is in /etc/sysconfig/puppetserver:
sudo vi /etc/sysconfig/puppetserver
Find the JAVA_ARGS line and adjust -Xms and -Xmx as needed:
JAVA_ARGS="-Xms2g -Xmx2g -Djruby.logger.class=com.puppetlabs.jruby_utils.jruby.Slf4jLogger"
The JAVA_BIN line should already point to /usr/bin/java, which is Java 21 on Rocky 10. No changes needed there.
Step 6: Start Puppet Server and Configure Firewall
Enable and start the Puppet Server service. The first start takes 30 to 60 seconds because the JVM needs to initialize and generate the CA certificate:
sudo systemctl enable --now puppetserver
Check the service status:
sudo systemctl status puppetserver
You should see active (running):
● puppetserver.service - puppetserver Service
Loaded: loaded (/usr/lib/systemd/system/puppetserver.service; enabled; preset: disabled)
Active: active (running)
Main PID: 2821 (java)
Memory: 1.2G
Verify Puppet Server is listening on port 8140:
ss -tlnp | grep 8140
Expected output:
LISTEN 0 50 *:8140 *:* users:(("java",pid=2821,fd=7))
Open port 8140 in the firewall so agents can connect:
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --add-port=8140/tcp --permanent
sudo firewall-cmd --reload
Verify the port is open:
sudo firewall-cmd --list-ports
You should see 8140/tcp listed.
Step 7: Configure and Connect the Puppet Agent
On the Puppet Agent node, configure it to point to the Puppet Server:
source /etc/profile.d/puppet-agent.sh
sudo puppet config set server puppet-server.example.com --section main
sudo puppet config set certname puppet-agent.example.com --section main
Run the agent for the first time. This generates a certificate signing request (CSR) and sends it to the server:
sudo puppet agent --test
The first run will pause because the certificate has not been signed yet. You will see output like this:
Info: Creating a new RSA SSL key for puppet-agent.example.com
Info: csr_attributes file loading from /etc/puppetlabs/puppet/csr_attributes.yaml
Info: Creating a new SSL certificate request for puppet-agent.example.com
Info: Certificate Request fingerprint (SHA256): DC:22:13:62:64:1A:D2:57:D6:74:03:99:D1:CF:2C:F9:...
Step 8: Sign the Agent Certificate on the Server
Back on the Puppet Server, list pending certificate requests:
sudo puppetserver ca list
You should see the agent’s request:
Requested Certificates:
puppet-agent.example.com (SHA256) DC:22:13:62:64:1A:D2:57:D6:74:03:99:D1:CF:2C:F9:...
Sign it:
sudo puppetserver ca sign --certname puppet-agent.example.com
The output confirms the signing:
Successfully signed certificate request for puppet-agent.example.com
Now run the agent again on the Agent node. This time it downloads the signed certificate and fetches the catalog:
sudo puppet agent --test
A successful first catalog run looks like this:
Info: Downloaded certificate for puppet-agent.example.com from https://puppet-server.example.com:8140/puppet-ca/v1
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Notice: Requesting catalog from puppet-server.example.com:8140 (10.0.1.10)
Notice: Catalog compiled by puppet-server.example.com
Info: Applying configuration version '1774358584'
Notice: Applied catalog in 0.01 seconds
The agent and server are now connected. Verify all signed certificates on the server:
sudo puppetserver ca list --all
You should see both certificates listed:
Signed Certificates:
puppet-server.example.com (SHA256) D3:2C:3F:54:... alt names: ["DNS:puppet-server.example.com", "DNS:puppet-server", "DNS:puppet"]
puppet-agent.example.com (SHA256) 82:BF:3F:91:...
Step 9: Write a Real Puppet Manifest (Nginx Deployment)
A Puppet manifest without a real workload is just a hello world. Let us deploy a complete Nginx setup to the agent node, including a custom server block, a managed index page, firewall rules, a system user, and NTP configuration. This tests package management, file management, service management, user management, and dependency ordering all in one manifest.
On the Puppet Server, create the site manifest:
sudo vi /etc/puppetlabs/code/environments/production/manifests/site.pp
Add the following manifest:
node 'puppet-agent.example.com' {
# Install and manage Nginx
package { 'nginx':
ensure => installed,
}
# Deploy a custom index page
file { '/usr/share/nginx/html/index.html':
ensure => file,
content => "<html><body><h1>Managed by Puppet</h1>
<p>Deployed by Puppet Server 8.7.0 on Rocky Linux 10</p>
</body></html>\n",
require => Package['nginx'],
}
# Custom Nginx server block on port 8080
file { '/etc/nginx/conf.d/custom.conf':
ensure => file,
content => "server {
listen 8080;
server_name puppet-agent.example.com;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /status {
stub_status on;
allow 10.0.1.0/24;
deny all;
}
}\n",
require => Package['nginx'],
notify => Service['nginx'],
}
# Start and enable Nginx
service { 'nginx':
ensure => running,
enable => true,
require => Package['nginx'],
}
# Install and enable firewalld
package { 'firewalld':
ensure => installed,
}
service { 'firewalld':
ensure => running,
enable => true,
require => Package['firewalld'],
}
# Create a deployment user
user { 'deploy':
ensure => present,
shell => '/bin/bash',
managehome => true,
comment => 'Deployment user managed by Puppet',
}
# Ensure NTP is configured
package { 'chrony':
ensure => installed,
}
service { 'chronyd':
ensure => running,
enable => true,
require => Package['chrony'],
}
}
This manifest installs Nginx with a custom server block on port 8080, deploys a managed index page, sets up firewalld, creates a deploy user, and ensures chrony (NTP) is running. The require and notify parameters enforce proper ordering, so Nginx restarts automatically when its config file changes.
Validate the manifest syntax before applying it:
sudo puppet parser validate /etc/puppetlabs/code/environments/production/manifests/site.pp
No output means the syntax is valid.
Step 10: Apply the Manifest on the Agent
On the Agent node, trigger a Puppet run to apply the manifest:
sudo puppet agent --test
Puppet installs all packages, creates files, starts services, and creates the user. The output shows each change:
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/Package[nginx]/ensure: created
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/File[/usr/share/nginx/html/index.html]/ensure: defined content as '{sha256}a45de93...'
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/File[/etc/nginx/conf.d/custom.conf]/ensure: defined content as '{sha256}e10d0cc...'
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/Service[nginx]/ensure: ensure changed 'stopped' to 'running'
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/Package[firewalld]/ensure: created
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/Service[firewalld]/ensure: ensure changed 'stopped' to 'running'
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/User[deploy]/ensure: created
Notice: Applied catalog in 402.88 seconds
Verify the deployment worked by testing the Nginx custom page:
curl http://localhost:8080/
You should see the Puppet-managed page content. Check the deploy user was created:
id deploy
The output confirms the user exists:
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy)
Open the Nginx and Puppet ports in firewalld on the agent:
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --add-port=8140/tcp --permanent
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --reload
Step 11: Test Drift Remediation
One of Puppet’s strongest features is automatic drift correction. If someone manually changes a managed file, Puppet reverts it on the next run. This is critical in production where unauthorized changes can break systems silently.
Simulate drift by manually overwriting the managed index page on the agent:
echo "HACKED! This was manually changed." | sudo tee /usr/share/nginx/html/index.html
Confirm the file was changed:
curl http://localhost:8080/
You will see the modified content:
HACKED! This was manually changed.
Now run the Puppet agent:
sudo puppet agent --test
Puppet detects the content change and corrects it:
Notice: /Stage[main]/Main/Node[puppet-agent.example.com]/File[/usr/share/nginx/html/index.html]/content:
content changed '{sha256}80fd72f3...' to '{sha256}a45de939...' (corrective)
Notice: Applied catalog in 0.11 seconds
Verify the original content is restored:
curl http://localhost:8080/
The Puppet-managed content is back. In production, the Puppet agent runs as a background service every 30 minutes by default, so drift gets corrected automatically without manual intervention.
Step 12: Enable the Puppet Agent Service
By default, the Puppet agent does not run as a background service after installation. Enable it so the agent checks in with the server every 30 minutes:
sudo puppet resource service puppet ensure=running enable=true
The output confirms the service is running:
service { 'puppet':
ensure => 'running',
enable => 'true',
provider => 'systemd',
}
You can adjust the check-in interval by setting runinterval in puppet.conf. For example, to check every 15 minutes:
sudo puppet config set runinterval 900 --section main
Rocky Linux 10 vs Rocky Linux 9 Differences
For teams running mixed environments, here are the key differences when running Puppet 8 on Rocky Linux 10 compared to Rocky Linux 9:
| Item | Rocky Linux 9 | Rocky Linux 10 |
|---|---|---|
| Puppet repo package | puppet8-release-el-9.noarch.rpm | puppet8-release-el-9.noarch.rpm (same) |
| puppet-agent install | dnf install puppet-agent | dnf install puppet-agent (works directly) |
| puppetserver install | dnf install puppetserver | rpm -ivh --nodeps (Java 17 dep missing) |
| Java version | OpenJDK 17 | OpenJDK 21 |
| Kernel | 5.14.x | 6.12.x |
| SELinux | Enforcing (no issues) | Enforcing (no issues) |
| Firewalld | Installed by default | May need manual install |
Once installed, Puppet 8 behaves identically on both OS versions. All manifests, modules, and Hiera data work without modification.
Troubleshooting Common Issues
Error: “nothing provides java-17-openjdk-headless needed by puppetserver”
This is the expected error when trying to install puppetserver via dnf install on Rocky Linux 10. EL-10 does not include Java 17 in its repositories. The fix is to install Java 21 first, then use rpm -ivh --nodeps to install the puppetserver RPM as described in Step 4.
Error: “Connection refused” when running puppet agent –test
This means the Puppet Server is not running or port 8140 is blocked by the firewall. Check the service status with systemctl status puppetserver and verify port 8140 is open with sudo firewall-cmd --list-ports. Also confirm the agent can resolve the server hostname with ping puppet-server.example.com.
Error: “certificate verify failed” on the agent
If you rebuilt the Puppet Server and the agent still has old certificates, clean the agent’s SSL directory and re-register:
sudo puppet ssl clean
sudo puppet agent --test
Then sign the new certificate request on the server.
Puppet Server takes too long to start
The first startup can take 60 to 120 seconds because the JVM compiles JRuby code. Subsequent starts are faster. If it consistently fails to start, check /var/log/puppetlabs/puppetserver/puppetserver.log for Java heap errors. Increase the heap size in /etc/sysconfig/puppetserver if you see OutOfMemoryError.
Puppet agent reports “Could not request certificate”
This usually means the agent cannot reach port 8140 on the server. Verify network connectivity with nc -zv puppet-server.example.com 8140. On the server, ensure the firewall allows the connection and that ss -tlnp | grep 8140 shows the port listening.
What port does Puppet use?
Puppet Server listens on TCP port 8140 by default. All agent-to-server communication (catalog requests, certificate signing, file serving) happens over this single port using HTTPS with mutual TLS authentication. You only need to open port 8140/tcp on the server’s firewall.
Wrapping Up
You now have a working Puppet 8 infrastructure on Rocky Linux 10 with a server managing an agent node that runs Nginx, firewalld, chrony, and a managed user account. The EL-9 packages work reliably on EL-10 with the Java 21 workaround, and once installed, everything behaves exactly as it would on Rocky 9. For production use, consider setting up Ansible alongside Puppet for ad-hoc tasks, adding more agent nodes, organizing your manifests into Puppet modules, and configuring Hiera for data separation.