Three operating models, not three syntaxes
The usual summary (Ansible is easy, Puppet scales, Chef is flexible) is a description of first impressions, not of what you will operate. The useful cut is who starts a run. Ansible runs from a control node, "the machine from which you run the Ansible CLI tools", and "is not normally installed on managed nodes"; the installation guide states "No daemons or database setup are required". Chef installs Chef Infra Client on every node, and "periodically, Chef Infra Client contacts the Chef Infra Server to retrieve the latest cookbooks", converging only if the node has drifted. Puppet installs puppet-agent on every node; the agent sends facts, "the primary server uses [them] to compile a catalog", the agent applies it and "sends a report back to the primary server".
That single difference drives the rest of this page. A push tool needs the control node to reach every host and hold credentials for it, and does nothing between runs. A pull tool needs every host to reach a server, carries its own identity, and keeps working while you sleep. Both Chef and Puppet also ship an agentless option for the cases push suits (Chef's Agentless Mode, Puppet's Bolt), and Ansible ships ansible-pull for the reverse, but each is documented as the secondary path.
Who talks to whom: the network and credential picture
Direction of the connection, per tool
| Tool | Who connects to whom | What must exist | Where credentials concentrate |
|---|---|---|---|
| Ansible | The control node opens SSH (or WinRM) to each managed node | Inbound SSH on every host, a user with a POSIX shell and sudo, Python on the target | On the control node: keys, vault passwords, inventory |
| Chef | chef-client on each node calls Chef Infra Server over HTTPS | Outbound HTTPS from every node and an RSA client key registered with the server | On the server: cookbooks and policy; a key pair per node |
| Puppet | puppet agent calls Puppet Server over HTTPS (port 8140 by default) | Outbound 8140 from every agent and a certificate signed by the built-in CA | On the server: environments, Hiera data and the CA |
Ansible's requirements are the host's own: "a user account that can connect through SSH to the node with an interactive POSIX shell" and Python to run "Ansible-generated Python code" (network modules are the exception). The credential concentration is on the control node, which holds keys or vault passwords for everything it manages; that is the machine to protect, and the reason larger estates put AWX or Ansible Automation Platform in front of it rather than a laptop.
Chef and Puppet invert the exposure. Nodes initiate; the server never needs inbound access to them. Chef Infra Client authenticates to the server "using RSA public key-pairs each time" it needs data, and Puppet "includes a built-in certificate authority for managing certificates", with servers and agents communicating "by HTTPS using SSL certificates". The cost is that the server becomes the thing that can change every node at once: whoever can upload a cookbook or merge a Puppet environment has fleet-wide write access, on the agents' schedule, without a human in the loop for each host.
Cadence and drift: what happens when nobody runs anything
Ansible: nothing. A playbook applies when invoked, and a host that someone edited by hand stays edited until the next invocation. Scheduling is external (AWX, AAP, cron, CI). ansible-pull exists to invert this, running "on each managed node, each set to run via cron" against a Git checkout, but the concepts page calls that setup "rare and not the recommended setup", and the CLI tools "are not designed to run concurrently with themselves".
Chef: every 1800 seconds. The --interval default is 1800, --splay adds "a random number between zero and splay" to spread load on the server, and both apply before each run. A node that drifted converges back within roughly half an hour, and every converge uploads run data to the server.
Puppet: every 30 minutes. runinterval has a documented default of 30m (and "a runinterval of 0 means "run continuously" rather than "never run""), splay is off by default and only delays the first run after a service restart, and report is true by default so every transaction reports to the server. noop mode is a setting as well as a flag: Puppet "will take no action, and will instead report the changes it would have made".
This is the difference that matters for compliance and for outages in equal measure. Continuous enforcement means a hand-fix on a Chef or Puppet node is reverted within the interval unless the code changes too; that is the point, and it is also why incident responders need a way to stop the agent or set noop before touching a box. With Ansible the fix persists, and so does whatever else drifted.
The code, where the models show
The three snippets below manage the same thing: a package, a rendered config file, and a service that restarts when the file changes. They are trimmed to the lines that differ in model, not in syntax.
- hosts: webbecome: truetasks:- name: nginx presentansible.builtin.package:name: nginxstate: present- name: render configansible.builtin.template:src: nginx.conf.j2dest: /etc/nginx/nginx.confnotify: reload nginx # queued, runs once, at the endhandlers:- name: reload nginxansible.builtin.service:name: nginxstate: reloaded
package 'nginx'template '/etc/nginx/nginx.conf' dosource 'nginx.conf.erb'notifies :reload, 'service[nginx]' # :delayed unless you say :immediatelyendservice 'nginx' doaction [:enable, :start]end# Ruby is available when the DSL is not enough; that is the escape hatch# and the maintenance burden in one line.
package { 'nginx':ensure => installed,}file { '/etc/nginx/nginx.conf':ensure => file,content => template('profile/nginx.conf.erb'),require => Package['nginx'],}service { 'nginx':ensure => running,enable => true,subscribe => File['/etc/nginx/nginx.conf'], # refresh on change}# manifest order is the default; relationships are what you rely on
Read the propagation lines. Ansible handlers "only run when notified" and run once at the end of the play "regardless of how many tasks notify it"; you flush them early with meta: flush_handlers. Chef notifications are ":delayed" by default, "queued up, and then executed at the end of a Chef Infra Client run", with :immediately and :before available per notification. Puppet resources refresh through notify, subscribe or the ~> arrow, and the built-in types that can refresh are "service, exec, and package". All three converge on the same idea; they differ in whether the ordering is the file order (Ansible, Chef) or a graph where manifest order is only the default and "to manage a group of resources in a specific order, explicitly declare such relationships" (Puppet).
The other thing the snippets show is where a general-purpose language sits. Chef puts Ruby inside the recipe: "the full power of Ruby is available for when you need a programming language". Puppet keeps the DSL declarative and pushes logic into functions, templates and Hiera data. Ansible keeps YAML declarative and pushes logic into Jinja2 expressions and Python modules. Teams that reach for the escape hatch often will feel most at home in Chef, and will also carry the most code that needs Ruby readers to maintain.
Server components you have to run
Ansible needs none. The control node is a machine with Python; everything else is optional, and "multiple control nodes are possible, but Ansible itself does not coordinate across them", which is the gap AWX and the commercial Ansible Automation Platform fill with scheduling, credentials and RBAC. Execution Environments package Ansible into containers so that the control node is reproducible.
Chef needs Chef Infra Server: an API "written in Erlang", PostgreSQL as "the data storage repository", Bookshelf for cookbook content, and a search index. Policy is Policyfile.rb resolved on the workstation into a Policyfile.lock.json that is uploaded and "used in each Chef Infra Client run that's managed by that particular policy name and policy group", which is the modern replacement for mutable roles and environments. Chef Workstation ships the testing tools (Cookstyle, ChefSpec, Test Kitchen); Chef Automate is the optional visibility layer. Local mode (chef-client -z) runs against an in-memory chef-zero for development.
Puppet needs Puppet Server, which "performs the role of the primary node and also runs an agent to configure itself", plus its built-in CA. PuppetDB "is an optional component, which is not required to run Puppet Server", but "is necessary for features that require access to historical data (for example, when exported resources are used)". Hiera separates data from code and ships inside puppet-agent along with Facter. Bolt is the agentless side: it "connects directly to remote targets with SSH or WinRM, so you are not required to install any agent software". The Puppet course covers the server, PuppetDB and Hiera setup hands-on.
Licensing and who owns the roadmap in 2026
This section is documented fact, and it changed recently enough that older comparisons are wrong. ansible-core is GPL-3.0 and community-maintained on a published schedule: 2.21 reached GA in May 2026 with critical fixes to November 2026 and security fixes to May 2027; control nodes need Python 3.12 to 3.14 and targets 3.9 to 3.14. Red Hat sells Ansible Automation Platform on top; the engine itself carries no production restriction.
Chef's source is Apache-2.0, but "before you can use distributions of Chef products, you must accept the Chef End User License Agreement", and Progress Chef's tiers are explicit: the Free tier covers "non-production workloads" for "personal and non-commercial use", Trial is 30 days non-production, and Commercial is what covers "production and non-production workloads". The licence is enforced at download, not at agent runtime, and a licence key is required to download binaries from the Chef portal or to run workflows that download packages at runtime such as knife bootstrap. Chef Infra Client 19.3.15 (May 2026) is the current release.
Puppet moved furthest. Perforce's lifecycle page states: "As of August 2026, Perforce develops and supports Puppet Core versions 8 and 9 as a proprietary product. As of April 2025, open source Puppet 7 and Puppet Core 7 are end of life (EOL)." The purchasing page says it directly: "To use Puppet Core in a production environment and enjoy the full range of features and benefits, purchase the product", and a free version is available "on a maximum of 25 nodes" for testing and development after signing an EULA. The community answer is OpenVox, hosted by Vox Pupuli. It began as a package mirror "to continue providing community packages when Perforce discontinued public packaging efforts in late Fall of 2024", describes itself as "a soft-fork" intended to keep downstream compatibility, and its own release notes state that "Puppet Open Source is no longer actively developed". OpenVox 8.29.0 was released on 4 September 2026 as a bug-fix and security release, alongside OpenVox Server, OpenVoxDB and OpenBolt.
Six fleets, three answers
Which model, by fleet
| Fleet and situation | Pick | Why | What you absorb |
|---|---|---|---|
| A few hundred hosts, mixed Linux and Windows, run by a team that also does deployments and ad hoc operations | Ansible | One tool for orchestration and configuration; no agents to roll out; SSH and WinRM are already there | No enforcement between runs; you build the schedule (AWX, AAP or CI) and protect the control node like a bastion |
| Thousands of long-lived hosts where a hand-fix must be reverted automatically and reported | Puppet (Core, PE, or OpenVox) | Agent pulls a catalog every 30 minutes and reports every run; noop gives a safe audit mode | A Puppet Server and CA to operate; a purchase from Perforce for production, or the OpenVox fork and its release cadence |
| An estate with existing cookbooks and Ruby fluency, moving to Policyfiles and Chef 19 | Chef | Policyfile locks give reproducible node policy; Agentless Mode covers the boxes you cannot install on | Chef Infra Server (Erlang, PostgreSQL, Bookshelf) to run and a Progress commercial licence for production |
| Regulated environment that wants continuous enforcement plus proof of the desired state per host | Puppet or Chef | Both report every run to a server you can query (PuppetDB, Chef Automate); Ansible reports only when invoked | Fleet-wide write access concentrates on the server; access to cookbooks or environments becomes a privileged path |
| Network devices, appliances and cloud APIs alongside servers | Ansible | Modules for targets that cannot run an agent or Python; network modules need no Python on the device | Playbooks for devices are imperative in practice; idempotence depends on each module |
| Small team, no budget for a server tier, wants pull-based convergence anyway | Ansible with ansible-pull, or OpenVox | ansible-pull runs from cron against Git; OpenVox gives the Puppet model without a Perforce purchase | ansible-pull is documented as rare and not recommended; OpenVox is a young fork whose compatibility promise you are relying on |
Trade-offs by model
Constraints first, then the tool
Settle three constraints before you compare features. Can the hosts reach a server, or must the server reach the hosts? Does a hand-fix have to be reverted automatically, or is that a change-management failure you want to see? Is there budget and appetite for a commercial agreement, and if not, is a community fork an acceptable dependency?
If the hosts cannot run an agent, or the same team needs orchestration and ad hoc runs, the answer is Ansible and the work is building the scheduler and hardening the control node. If enforcement between runs is a requirement, the answer is Puppet or Chef, and the decision between them is whether you are buying from Perforce or Progress, or betting on OpenVox, more than it is about DSL taste. All three paths are documented, current and supportable. Syntax is the last thing worth comparing; the operating model and the invoice arrive whether or not you looked at them first.