Install and Configure Puppet
Updated by Linode Written by Elle Krout
DeprecatedThis guide has been deprecated and is no longer being maintained.Please refer to the updated version of this guide.
Puppet is a configuration automation platform that simplifies various system administrator tasks. Puppet uses a client/server model where the managed servers, called Puppet agents, talk to and pull down configuration profiles from the Puppet master.

Puppet is written in its own custom language, meant to be accessible to system administrators. A module, located on the Puppet master, describes the desired system. The Puppet software then translates the module into code and alters the agent servers as needed when the puppet agent command is run on an agent node or automatically at designated intervals.
Puppet can be used to manage multiple servers across various infrastructures, from a group of personal servers up to an enterprise level operation. It is intended to run on Linux and other Unix-like operating systems, but has also been ported to Windows. For the purpose of this guide, however, we will be working with an Ubuntu 16.04 LTS master server and two agent nodes: one Ubuntu 16.04, and one CentOS 7.
NoteBegin this guide as therootuser. A limited user with administrative privileges will be configured in later steps.
Before You Begin
- You should have three available Linodes, one of which has at least four CPU cores for the Puppet master. A Linode 8GB plan is recommended. The two other nodes can be of any plan size, depending on how you intend to use them after Puppet is installed and configured. 
- Follow the Getting Started guide and ensure your Linodes are configured to use the same timezone. - Note - For ease of use, set the Puppet master server’s hostname to - puppet, and have a valid fully-qualified domain name (FQDN).- To check your hostname, run - hostnameand to check your FQDN, run- hostname -f.
Puppet Master
Install Puppet Master
- Install the - puppetlabs-releaserepository into Ubuntu 16.04 and update your system. This process downloads a- .debfile that will configure the repositories for you:- wget https://apt.puppetlabs.com/puppetlabs-release-pc1-xenial.deb dpkg -i puppetlabs-release-pc1-xenial.deb apt update- Note - If you wish to run another Linux distribution as your master server, the initial - .debfile can be substituted for another distribution based on the following formats:- Red Hat-based systems: - wget https://yum.puppetlabs.com/puppetlabs-release-pc1-OS-VERSION.noarch.rpm- Debian-based systems: - wget https://apt.puppetlabs.com/puppetlabs-release-pc1-VERSION.deb- Any Ubuntu-specific commands will then have to be amended for the proper distribution. More information can be found in Puppet’s Installation Documentation or our guide to package management. 
- Install the - puppetmaster-passengerpackage:- apt install puppetmaster-passenger
- The default Puppet installation may start Apache and configure it to listen on the same port as Puppet. Stop Apache to avoid this conflict (if using CentOS7, change - apache2in this example to- httpd):- systemctl stop apache2
- Ensure you have the latest version of Puppet by running: - puppet resource package puppetmaster ensure=latest
Configure Puppet Master
- Update - /etc/puppet/puppet.confand add the- dns_alt_namesline to the section- [main], replacing- puppet.example.comwith your own FQDN:- /etc/puppet/puppet.conf
- 
1 2[main] dns_alt_names=puppet,puppet.example.com
 
- Start the Puppet master: - systemctl start puppetmaster- By default, the Puppet master process listens for client connections on port 8140. If the - puppetmasterservice fails to start, check that the port is not already in use:- netstat -anpl | grep 8140
Puppet Agents
Install Puppet Agent
On agent nodes running Ubuntu 16.04 or other Debian-based distributions, use this command to install Puppet:
apt install puppet
On agent nodes running CentOS 7 or other Red Hat systems, follow these steps:
- For CentOS 7 only, add the Puppet Labs repository: - rpm -ivh https://yum.puppetlabs.com/el/7/products/x86_64/puppetlabs-release-22.0-2.noarch.rpm- Note If you’re on a Red Hat system other than CentOS 7, skip this step.
- Install the Puppet agent: - yum install puppet
Configure Puppet Agent
- Modify your Puppet Agent’s host file to resolve the Puppet master IP as - puppet:- /etc/hosts
- 
1198.51.100.0 puppet
 
- Add the - servervalue to the- [main]section of the node’s- puppet.conffile, replacing- puppet.example.comwith the FQDN of your Puppet master:- /etc/puppet/puppet.conf
- 
1 2[main] server=puppet.example.com
 
- Restart the Puppet service: - systemctl restart puppet
Generate and Sign Certificates
- On each agent, generate a certificate for the Puppet master to sign: - puppet agent -t- This will output an error, stating that no certificate has been found. This error is because the generated certificate needs to be approved by the Puppet master. 
- Log in to your Puppet master and list the certificates that need approval: - puppet cert list- It should output a list with your agent node’s hostname. 
- Approve the certificates, replacing - hostname.example.comwith the hostname of each agent node:- puppet cert sign hostname.example.com
- Return to the Puppet agent nodes and run the Puppet agent again: - puppet agent -t
Add Modules to Configure Agent Nodes
Both the Puppet master and agent nodes configured above are functional, but not secure. Based on concepts from the Securing Your Server guide, a limited user and a firewall should be configured. This can be done on all nodes through the creation of basic Puppet modules, shown below.
NoteThis is not meant to provide a basis for a fully-hardened server, and is intended only as a starting point. Alter and add firewall rules and other configuration options, depending on your specific needs.
Add a Limited User
- From the Puppet master, navigate to the - /etc/puppet/modulesdirectory and create your new module for adding user accounts, then- cdinto that directory:- mkdir /etc/puppet/modules/accounts cd /etc/puppet/modules/accounts
- Create the following directories, which are needed to have a functioning module: - mkdir {examples,files,manifests,templates}- The - examplesdirectory allows you to test the module locally.- filescontains any static files that may need to be edited or added.- manifestscontains the actual Puppet code for the module, and- templatescontains any non-static files that may be needed.
- Move to the - manifestsdirectory and create your first class, called- init.pp. All modules require an- init.ppfile to be used as the main definition file of a module.- cd manifests
- Within the - init.ppfile, define a limited user to use instead of- root, replacing all instances of- usernamewith your chosen username:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5 6 7 8 9 10 11class accounts { user { 'username': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', } }
 - The - init.ppfile initially defines the- accountsclass. It then calls for the- userresource, where a- usernameis defined. The- ensurevalue is set to ensure that the user exists (is present). The- homevalue should be set to the user’s home directory path.- shelldefines the shell type, in this instance the bash shell.- managehomenotes that the home directory should be created. Finally,- gidsets the primary group for the user.
- Although the primary group is set to share the username, the group itself has not been created. Save and exit - init.pp. Then, create a new file called- groups.ppand add the following contents. This file will be used to create the user’s group. Again, replace- usernamewith your chosen username:- /etc/puppet/modules/accounts/manifests/groups.pp
- 
1 2 3 4 5 6 7class accounts::groups { group { 'username': ensure => present, } }
 - Include this file by adding - include groupsto the- init.ppfile, within the- accountsclass:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5class accounts { include groups ... }
 
- This user should have privileges so that administrative tasks can be performed. Because we have agent nodes on both Debian- and Red Hat-based systems, the new user needs to be in the - sudogroup on Debian systems, and the- wheelgroup on Red Hat systems. This value can be set dynamically through the use of a selector and facter, a program included in Puppet that keeps track of information, or facts, about every server. Add a selector statement to the top of the- init.ppfile within the accounts class brackets, defining the two options:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12class accounts { $rootgroup = $osfamily ? { 'Debian' => 'sudo', 'RedHat' => 'wheel', default => warning('This distribution is not supported by the Accounts module'), } user { 'username': ... }
 - This command sequence tells Puppet that within the accounts module the variable - $rootgroupshould evaluate, using facter, the operating system family (- $osfamily), and if the value returned is- Debian, to set the- $rootgroupvalue to- sudo. If the value returned is- RedHat, this same value should be set to- wheel; otherwise, the- defaultvalue will output a warning that the distribution selected is not supported by this module.- Note The- userdefinition will include the- $rootgroup, and the Puppet Configuration Language executes code from top to bottom. You must define the- $rootgroupbefore the- userso that it can be accessed.
- Add the - groupsvalue to the user resource, calling to the- $rootgroupvariable defined in the previous step:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5 6 7 8user { 'username': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', groups => "$rootgroup", }
 - The value - "$rootgroup"is enclosed in double quotes (“) instead of single quotes (‘) because it is a variable. Any value enclosed within single quotes will be added as typed in your module; anything enclosed in double quotes can accept variables.
- The final value that needs to be added is the - password. Since we do not want to use plain text, it should be fed to Puppet as a SHA1 digest, which is supported by default. Set a password from the terminal:- openssl passwd -1- You will be prompted to enter your password and confirm. A hashed password will be output. This should then be copied and added to the - userresource:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12 13class accounts { user { 'username': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', groups => "$rootgroup", password => '$1$07JUIM1HJKDSWm8.NJOqsP.blweQ..3L0', } }
 - Caution The hashed password must be included in single quotes (‘).
- After saving your changes, use the puppet parser to ensure that the code is correct: - puppet parser validate init.pp- Any errors that need to be addressed will be logged to standard output. If nothing is returned, your code is valid. 
- Before the module can be tested further, navigate to the - examplesdirectory and create another- init.ppfile, this time to call to the- accountsmodule:- cd ../examples- /etc/puppet/modules/accounts/examples/init.pp
- 
1include accounts
 - After adding this line, save and exit the file. 
- While still in the - examplesdirectory, test the module without making changes:- puppet apply --noop init.pp- Note The- --noopparameter prevents Puppet from actually running the module.- It should return: - Notice: Compiled catalog for puppet.example.com in environment production in 0.26 seconds Notice: /Stage[main]/Accounts::Groups/Group[username]/ensure: current_value absent, should be present (noop) Notice: Class[Accounts::Groups]: Would have triggered 'refresh' from 1 events Notice: /Stage[main]/Accounts/User[username]/ensure: current_value absent, should be present (noop) Notice: Class[Accounts]: Would have triggered 'refresh' from 1 events Notice: Stage[main]: Would have triggered 'refresh' from 2 events Notice: Finished catalog run in 0.02 seconds
- Again from the - examplesdirectory, run- puppet applyto make these changes to the Puppet master server:- puppet apply init.pp
- Log out as - rootand log in to the Puppet master as your new user. The rest of this guide will be run by this user.
Edit SSH Settings
Although a new user has successfully been added to the Puppet master, the account is still not secure. Root access should be disabled for the server before continuing.
- Navigate to - fileswithin the- accountmodule directory:- cd /etc/puppet/modules/accounts/files
- Copy the - sshd_configfile to this directory:- sudo cp /etc/ssh/sshd_config .
- Open the file with - sudo, and set the- PermitRootLoginvalue to- no:- /etc/puppet/modules/accounts/files/sshd_config
- 
1PermitRootLogin no
 
- Navigate back to the - manifestsdirectory and, using- sudo, create a file called- ssh.pp. Use the- fileresource to replace the default configuration file with the one managed by Puppet:- cd ../manifests- /etc/puppet/modules/accounts/manifests/ssh.pp
- 
1 2 3 4 5 6 7 8class accounts::ssh { file { '/etc/ssh/sshd_config': ensure => present, source => 'puppet:///modules/accounts/sshd_config', } }
 - Note The- filedirectory is omitted from the- sourceline because the- filesfolder is the default location of files. For more information on the format used to access resources in a module, refer to the official Puppet module documentation.
- Create a second resource to restart the SSH service and set it to run whenever - sshd_configis changed. This will also require a selector statement because the SSH service is called- sshon Debian systems and- sshdon Red Hat:- /etc/puppet/modules/accounts/manifests/ssh.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18class accounts::ssh { $sshname = $osfamily ? { 'Debian' => 'ssh', 'RedHat' => 'sshd', default => warning('This distribution is not supported by the Accounts module'), } file { '/etc/ssh/sshd_config': ensure => present, source => 'puppet:///modules/accounts/sshd_config', notify => Service["$sshname"], } service { "$sshname": hasrestart => true, } }
 
- Include the - sshclass within- init.pp:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5class accounts { include groups include ssh ...
 - Your complete - init.ppwill look similar to this:- /etc/puppet/modules/accounts/manifests/init.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21class accounts { include groups include ssh $rootgroup = $osfamily ? { 'Debian' => 'sudo', 'RedHat' => 'wheel', default => warning('This distro not supported by Accounts module'), } user { 'example': ensure => present, home => '/home/username', shell => '/bin/bash', managehome => true, gid => 'username', groups => "$rootgroup", password => '$1$07JUIM1HJKDSWm8.NJOqsP.blweQ..3L0' } }
 
- Run the Puppet parser, then navigate to the - examplesdirectory to test and run- puppet apply:- sudo puppet parser validate ssh.pp cd ../examples sudo puppet apply --noop init.pp sudo puppet apply init.pp- Note - You may see the following line in your output when validating: - Error: Removing mount "files": /etc/puppet/files does not exist or is not a directory- This refers to a Puppet configuration file, not the module resource you’re trying to copy. If this is the only error in your output, the operation should still succeed. 
- To ensure that the - sshclass is working properly, log out and then try to log in as- root. You should not be able to do so.
Add and Configure IPtables
In this section, we’ll configure firewall rules using iptables. However, these rules will not persist across reboots by default. To avoid this, install the appropriate package on each node (both master and agents) before proceeding:
Ubuntu/Debian:
sudo apt install iptables-persistent
CentOS 7:
CentOS 7 uses firewalld by default as a controller for iptables. Be sure firewalld is stopped and disabled before starting to work directly with iptables:
sudo systemctl stop firewalld && sudo systemctl disable firewalld
sudo yum install iptables-services
- On your Puppet master node, install Puppet Lab’s firewall module from the Puppet Forge: - sudo puppet module install puppetlabs-firewall- The module will be installed in your - /etc/puppet/modulesdirectory.
- Navigate to the - manifestsdirectory under the new- firewallmodule:- cd /etc/puppet/modules/firewall/manifests/
- Create a file titled - pre.pp, which will contain all basic networking rules that should be run first:- /etc/puppet/modules/firewall/manifests/pre.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55class firewall::pre { Firewall { require => undef, } # Accept all loopback traffic firewall { '000 lo traffic': proto => 'all', iniface => 'lo', action => 'accept', }-> #Drop non-loopback traffic firewall { '001 reject non-lo': proto => 'all', iniface => '! lo', destination => '127.0.0.0/8', action => 'reject', }-> #Accept established inbound connections firewall { '002 accept established': proto => 'all', state => ['RELATED', 'ESTABLISHED'], action => 'accept', }-> #Allow all outbound traffic firewall { '003 allow outbound': chain => 'OUTPUT', action => 'accept', }-> #Allow ICMP/ping firewall { '004 allow icmp': proto => 'icmp', action => 'accept', } #Allow SSH connections firewall { '005 Allow SSH': dport => '22', proto => 'tcp', action => 'accept', }-> #Allow HTTP/HTTPS connections firewall { '006 HTTP/HTTPS connections': dport => ['80', '443'], proto => 'tcp', action => 'accept', } }
 - Each rule is explained via commented text. More information can also be found on the Puppet Forge Firewall page. 
- In the same directory create - post.pp, which will run any firewall rules that need to be input last:- /etc/puppet/modules/firewall/manifests/post.pp
- 
1 2 3 4 5 6 7 8 9class firewall::post { firewall { '999 drop all': proto => 'all', action => 'drop', before => undef, } }
 - These rules will direct the system to drop all inbound traffic that is not already permitted in the firewall. 
- Run the Puppet parser on both files to ensure the code does not return any errors: - sudo puppet parser validate pre.pp sudo puppet parser validate post.pp
- Move up a directory, create a new - examplesdirectory, and navigate to it:- cd .. sudo mkdir examples cd examples
- Within - examples, create an- init.ppfile to test the firewall on the Puppet master:- /etc/puppet/modules/firewall/examples/init.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16resources { 'firewall': purge => true, } Firewall { before => Class['firewall::post'], require => Class['firewall::pre'], } class { ['firewall::pre', 'firewall::post']: } firewall { '200 Allow Puppet Master': dport => '8140', proto => 'tcp', action => 'accept', }
 - This code block ensures that - pre.ppand- post.pprun properly, and adds a firewall rule to the Puppet master to allow nodes to access it.
- Run the - init.ppfile through the Puppet parser and then test to see if it will run:- sudo puppet parser validate init.pp sudo puppet apply --noop init.pp- If successful, run the - puppet applywithout the- --noopoption:- sudo puppet apply init.pp
- Once Puppet is done running, check the iptables rules: - sudo iptables -L- It should return: - Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- anywhere anywhere /* 000 lo traffic */ REJECT all -- anywhere 127.0.0.0/8 /* 001 reject non-lo */ reject-with icmp-port-unreachable ACCEPT all -- anywhere anywhere /* 002 accept established */ state RELATED,ESTABLISHED ACCEPT icmp -- anywhere anywhere /* 004 allow icmp */ ACCEPT tcp -- anywhere anywhere multiport ports ssh /* 005 Allow SSH */ ACCEPT tcp -- anywhere anywhere multiport ports http,https /* 006 HTTP/HTTPS connections */ ACCEPT tcp -- anywhere anywhere multiport ports 8140 /* 200 Allow Puppet Master */ DROP all -- anywhere anywhere /* 999 drop all */ Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination ACCEPT tcp -- anywhere anywhere /* 003 allow outbound */
Add Modules to the Agent Nodes
Now that the accounts and firewall modules have been created, tested, and run on the Puppet master, it is time to add them to the Puppet agent nodes created earlier.
- From the Puppet master, navigate to - /etc/puppet/manifests.- cd /etc/puppet/manifests
- List all available agent nodes: - sudo puppet cert list -all
- Create the file - site.ppto define which nodes will take what modules. Replace- ubuntuagent.example.comand- centosagent.example.comwith the FQDNs of your agent nodes:- /etc/puppet/manifests/site.pp
- 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31node 'ubuntuagent.example.com' { include accounts resources { 'firewall': purge => true, } Firewall { before => Class['firewall::post'], require => Class['firewall::pre'], } class { ['firewall::pre', 'firewall::post']: } } node 'centosagent.example.com' { include accounts resources { 'firewall': purge => true, } Firewall { before => Class['firewall::post'], require => Class['firewall::pre'], } class { ['firewall::pre', 'firewall::post']: } }
 - This includes the - accountsmodule and uses the same firewall settings as above to ensure that the firewall rules are applied properly.
- On each Puppet agent node, enable the - puppet agentcommand:- puppet agent --enable
- Run the Puppet agent: - puppet agent -t
- To ensure the Puppet agent worked, log in as the limited user that was created and check the iptables: - sudo iptables -L
Congratulations! You’ve successfully installed Puppet on a master and two agent nodes. Now that you’ve confirmed everything is working, you can create additional modules to automate configuration management on your agent nodes. For more information, see Puppet module fundamentals. You can also install and use those modules others have created on the Puppet Forge.
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
Join our Community
Find answers, ask questions, and help others.
This guide is published under a CC BY-ND 4.0 license.