Chef Certified common mistakes
Chef Certified · Understand the traps and how to avoid them.
Writing a recipe that shells out with `execute 'apt-get update'` or `bash` blocks instead of using the package/resource abstractions, then wondering why the run is not idempotent and fails on the second converge.
Candidates used to ad-hoc shell scripting default to command execution because it feels faster to write, and they do not internalize that every Chef resource must describe desired state, not a sequence of actions.
What to do instead
For each shell command, ask which built-in resource expresses the end state: package/apt_package for installs, service for daemons, template/file for config, directory for paths. When a command is genuinely unavoidable, wrap it in execute with a not_if/only_if guard (or creates) so it converges only once. Verify by running chef-client twice and confirming the second run reports zero updated resources.
Putting environment-specific values (paths, ports, package versions) directly in recipe code, or conversely trying to read node attributes in the `metadata.rb` and getting confused when it fails.
Candidates conflate the compile-time and converge-time phases of a Chef run and do not have a clear model of where attributes, recipes, and metadata are evaluated, so they place logic in whichever file they happen to be editing.
What to do instead
Learn the run phases: metadata is loaded first and is static Ruby only; attributes are evaluated before recipes; recipes compile resources then converge them. Move tunables into attributes files or wrapper cookbooks, reference them as node['cookbook']['key'], and keep metadata.rb limited to static declarations like depends and supports.
Using `include_recipe` to compose cookbooks and then overwriting the included recipe's attributes in the wrapper recipe, expecting the override to take effect.
Candidates do not realize attribute precedence is fixed (default < normal < override, with role/environment and policyfile levels interleaved) and that reassigning node.default inside a recipe after include_recipe runs too late for the included recipe's compile step.
What to do instead
Set attribute overrides in the wrapper cookbook's attributes/default.rb (or a role/environment/policyfile) before recipes compile, not inside the recipe body after an include. Use node.override sparingly and prefer a dedicated wrapper attribute namespace. Test with chef-client -o 'recipe[wrapper]' and inspect the node object with chef-shell or ohai output.
Treating `chef-client` runs as fire-and-forget and never checking the exit codes or the run summary, so partial converges and deprecation warnings go unnoticed until an audit.
Candidates focus on getting a recipe to converge once locally and skip the operational habits of reading the run report, which is exactly what the exam tests under infrastructure management.
What to do instead
Read the end-of-run resource summary (updated/skipped/failed counts) and the exit status: 0 success, non-zero for failure, and distinct codes for things like unrecoverable errors. Use chef-client --why-run to preview changes, -l debug when diagnosing, and wire the handler/reporting output into your workflow so failures are visible.
Assuming a node's run list and roles are the source of truth while ignoring that a Policyfile or Chef Infra Server-assigned policy can override them, then debugging changes that never apply.
Candidates learn the role/run-list model first and do not account for policyfiles, which pin cookbook revisions and replace roles/environments; the node converges the policy, not the run list they edited locally.
What to do instead
Check what the node is actually converging: chef-client -D shows the resolved run list and cookbook versions, and the node's policy_name/policy_group attributes reveal policyfile mode. If a policy is in use, update the policyfile and push with chef install/chef push, not the run list.
Bootstrapping nodes with the wrong validator key or client name and then trying to fix it by re-running bootstrap, leaving duplicate or orphaned node and client objects on the server.
Candidates treat bootstrap as a one-shot command and do not understand that it registers a client and node object on the Chef Infra Server; a failed bootstrap can leave partial state that blocks the retry.
What to do instead
Before retrying, delete the stale client and node (knife client delete NAME, knife node delete NAME, or the equivalent chef-server-ctl/API calls), confirm the validator client is valid, and pass explicit -N node name and --node-ssl-verify-mode none only in lab environments. After bootstrap, confirm the node appears with knife node list and has the intended run list.
Writing InSpec controls that merely assert a file exists, or that pass vacuously because the resource target was empty, instead of asserting the specific property the compliance requirement names.
Candidates translate a requirement into the first resource that comes to mind (often file or describe file) without checking which resource exposes the needed property, and they do not test that a failing case actually fails.
What to do instead
Map each requirement to the precise resource and property, e.g. describe file('/etc/ssh/sshd_config') { its('content') { should match /^PermitRootLogin no$/ } } or describe sshd_config { its('PermitRootLogin') { should cmp 'no' } }. Prove the control is meaningful by running it against a deliberately non-compliant node and confirming it fails.
Confusing Chef Infra Client compliance-phase configuration with the `inspec` CLI, so profiles are scanned locally but never reported to the server, or scan results are expected but the compliance phase was never enabled.
Candidates learn InSpec standalone and do not realize the audit/compliance phase is a separate, opt-in part of the chef-client run configured via node['audit'] attributes or a compliance profile in the policyfile.
What to do instead
Enable the compliance phase explicitly (for example node.default['audit']['reporter'] = 'chef-server-automate' or the appropriate reporter, plus node.default['audit']['profiles'] entries), or attach the compliance profile in the policyfile. Confirm the run summary shows audit results and that the reporter target is reachable.
Defining a `chef_client` schedule or cron entry as the only way jobs run, without verifying that the timer actually fires, that the client can authenticate, and that failures surface anywhere.
Candidates treat scheduling as configuration-only and never close the loop: a scheduled run that silently fails authentication or is blocked by a lock looks identical to a healthy system.
What to do instead
After configuring the schedule, verify it with the platform's scheduler (systemctl list-timers, crontab -l), check the chef-client log for a completed run, and confirm the node reports to the server (knife node show NAME -a ohai_time). Add a report handler or log shipping so failed scheduled runs are visible, and account for the client lock so overlapping runs do not silently skip.
Pushing a run-list or policy change to all nodes at once and discovering mid-rollout that a cookbook version is broken, with no way to stop or roll back the fleet.
Candidates optimize for getting the change out and skip staged rollout, version pinning, and rollback planning, which the job-orchestration domain explicitly tests.
What to do instead
Pin cookbook or policy versions, roll out to a canary group first, and verify health before widening. Keep the previous known-good version available so you can re-pin and re-push to roll back. Use search or node attributes to target batches, and confirm convergence per batch with the run summary before proceeding.