{"id":164926,"date":"2026-03-28T21:46:31","date_gmt":"2026-03-28T18:46:31","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=164926"},"modified":"2026-03-28T21:46:32","modified_gmt":"2026-03-28T18:46:32","slug":"claude-code-ssh-server-management","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/claude-code-ssh-server-management\/","title":{"rendered":"Manage Servers with Claude Code via SSH"},"content":{"rendered":"\n<p>Every sysadmin SSHes into servers daily. Claude Code can do it too, and it remembers context across commands. You tell it &#8220;the web server on 10.0.1.5 is down, fix it&#8221; and it checks the service status, reads the error log, identifies the config problem, patches it, re-validates, and restarts. No scripting. No copying error messages back and forth.<\/p>\n\n\n\n<p>This is the practical companion to the <a href=\"https:\/\/computingforgeeks.com\/claude-code-devops-engineers\/\" target=\"_blank\" rel=\"noreferrer noopener\">Claude Code for DevOps Engineers<\/a> pillar guide. Everything below was tested live on a Rocky Linux 10.1 OpenStack VM (10.0.1.50) and two Proxmox hypervisors (Debian 12). The commands, the output, the errors are all real. You can reproduce every demo by pointing Claude Code at your own servers.<\/p>\n\n\n\n<p><em>Verified working: <strong>March 2026<\/strong> on Rocky Linux 10.1 (kernel 6.12), Debian 12 (Proxmox 8.x), Nginx 1.26.3, Certbot via EPEL<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What You Need<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/code.claude.com\/docs\/en\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">Claude Code<\/a> installed (<code>curl -fsSL https:\/\/claude.ai\/install.sh | bash<\/code>)<\/li>\n\n<li>SSH key-based access to at least one Linux server (password auth works but key-based is smoother)<\/li>\n\n<li>A Claude subscription (Pro, Max, or Team) or Anthropic API key<\/li>\n\n<li>Tested on: Rocky Linux 10.1 (RHEL family), Debian 12 (Proxmox). Commands adapt for Ubuntu automatically<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Provision a Web Server from Scratch<\/h2>\n\n\n\n<p>The first demo shows the complete workflow: install software, configure it, set up SSL, and verify. One conversation, zero manual SSH sessions.<\/p>\n\n\n\n<p>Open Claude Code and type:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SSH into 10.0.1.50 as rocky. Install Nginx, create a custom landing\npage showing the hostname and OS version, open firewall ports 80 and 443,\nset up Let's Encrypt SSL for sshdemo.computingforgeeks.com, and verify\nthe site loads over HTTPS.<\/code><\/pre>\n\n\n\n<p>Claude Code connects and runs the install:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo dnf install -y nginx\"<\/code><\/pre>\n\n\n\n<p>Nginx 1.26.3 installs from the Rocky Linux 10 base repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Installed:\n  nginx-2:1.26.3-1.el10.x86_64            nginx-core-2:1.26.3-1.el10.x86_64\n  nginx-filesystem-2:1.26.3-1.el10.noarch rocky-logos-httpd-100.4-7.el10.noarch\n\nComplete!<\/code><\/pre>\n\n\n\n<p>It creates the custom landing page, starts the service, and verifies:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo systemctl enable --now nginx\"\nssh rocky@10.0.1.50 \"systemctl status nginx --no-pager\"<\/code><\/pre>\n\n\n\n<p>The service comes up clean:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u25cf nginx.service - The nginx HTTP and reverse proxy server\n     Loaded: loaded (\/usr\/lib\/systemd\/system\/nginx.service; enabled; preset: disabled)\n     Active: active (running) since Fri 2026-03-27 23:08:59 UTC; 32ms ago\n    Process: 4772 ExecStartPre=\/usr\/sbin\/nginx -t (code=exited, status=0\/SUCCESS)\n    Process: 4774 ExecStart=\/usr\/sbin\/nginx (code=exited, status=0\/SUCCESS)\n   Main PID: 4775 (nginx)\n      Tasks: 2 (limit: 10852)\n     Memory: 2.3M (peak: 2.8M)\n        CPU: 40ms\n     CGroup: \/system.slice\/nginx.service\n             \u251c\u25004775 \"nginx: master process \/usr\/sbin\/nginx\"\n             \u2514\u25004776 \"nginx: worker process\"<\/code><\/pre>\n\n\n\n<p>For SSL, Claude Code notices certbot isn&#8217;t in the default repos, installs EPEL first, then runs certbot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo dnf install -y epel-release\"\nssh rocky@10.0.1.50 \"sudo dnf install -y certbot python3-certbot-nginx\"\nssh rocky@10.0.1.50 \"sudo certbot certonly --standalone -d sshdemo.computingforgeeks.com --non-interactive --agree-tos -m admin@example.com\"<\/code><\/pre>\n\n\n\n<p>Certificate obtained successfully:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Successfully received certificate.\nCertificate is saved at: \/etc\/letsencrypt\/live\/sshdemo.computingforgeeks.com\/fullchain.pem\nKey is saved at:         \/etc\/letsencrypt\/live\/sshdemo.computingforgeeks.com\/privkey.pem\nThis certificate expires on 2026-06-25.<\/code><\/pre>\n\n\n\n<p>Claude Code then writes the Nginx SSL server block, reloads the config, and confirms HTTPS serves correctly. The final verification:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -sI https:\/\/sshdemo.computingforgeeks.com\/ | head -5<\/code><\/pre>\n\n\n\n<p>Returns HTTP\/2 200 with valid SSL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/2 200\nserver: nginx\/1.26.3\ndate: Fri, 27 Mar 2026 23:10:59 GMT\ncontent-type: text\/html\ncontent-length: 273<\/code><\/pre>\n\n\n\n<p>The HTTPS site is live and serving over HTTP\/2 with a valid Let&#8217;s Encrypt certificate:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/01-nginx-https-demo.png\" alt=\"Nginx web page showing Provisioned by Claude Code via SSH on Rocky Linux 10.1 served over HTTPS\" class=\"wp-image-164928\" width=\"1920\" height=\"941\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/01-nginx-https-demo.png 1920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/01-nginx-https-demo-300x147.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/01-nginx-https-demo-1024x502.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/01-nginx-https-demo-768x376.png 768w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/01-nginx-https-demo-1536x753.png 1536w\" sizes=\"auto, (max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<p>From zero to a fully provisioned HTTPS web server in a single Claude Code conversation. No Ansible, no Terraform, no shell script. Just a natural language description of what you need. For a deep reference on <a href=\"https:\/\/computingforgeeks.com\/letsencrypt-ssl-certificate-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">setting up Let&#8217;s Encrypt certificates on Linux<\/a>, that guide covers the manual steps this demo automated.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Diagnose and Fix a Broken Service<\/h2>\n\n\n\n<p>This demo is worth bookmarking. We break Nginx on purpose, then tell Claude Code the server is down and let it figure out the rest. No hints, no &#8220;check the config file.&#8221; Just &#8220;it&#8217;s broken, fix it.&#8221;<\/p>\n\n\n\n<p>First, introduce a real config error by removing a semicolon from the <code>events<\/code> block:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo sed -i 's\/worker_connections 1024;\/worker_connections 1024\/' \/etc\/nginx\/nginx.conf\"\nssh rocky@10.0.1.50 \"sudo systemctl restart nginx\"<\/code><\/pre>\n\n\n\n<p>Nginx fails:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Job for nginx.service failed because the control process exited with error code.\nSee \"systemctl status nginx.service\" and \"journalctl -xeu nginx.service\" for details.<\/code><\/pre>\n\n\n\n<p>Now tell Claude Code to diagnose it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>The web server on 10.0.1.50 is down. SSH in as rocky, figure out\nwhat's wrong, fix it, and verify it's back up.<\/code><\/pre>\n\n\n\n<p>Claude Code runs through its own diagnostic sequence. No instructions needed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Check service status<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"systemctl status nginx --no-pager\"<\/code><\/pre>\n\n\n\n<p>The status reveals the failure with the exact error line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u00d7 nginx.service - The nginx HTTP and reverse proxy server\n     Active: failed (Result: exit-code) since Fri 2026-03-27 23:11:46 UTC; 8s ago\n    Process: 5618 ExecStartPre=\/usr\/sbin\/nginx -t (code=exited, status=1\/FAILURE)\n\nMar 27 23:11:46 test-rocky-10 nginx[5618]: nginx: [emerg] unexpected \"}\" in \/etc\/nginx\/nginx.conf:15\nMar 27 23:11:46 test-rocky-10 nginx[5618]: nginx: configuration file \/etc\/nginx\/nginx.conf test failed\nMar 27 23:11:46 test-rocky-10 systemd[1]: nginx.service: Control process exited, code=exited, status=1\/FAILURE\nMar 27 23:11:46 test-rocky-10 systemd[1]: Failed to start nginx.service<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Validate config for the exact error<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo nginx -t\"<\/code><\/pre>\n\n\n\n<p>Confirms the problem:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nginx: [emerg] unexpected \"}\" in \/etc\/nginx\/nginx.conf:15\nnginx: configuration file \/etc\/nginx\/nginx.conf test failed<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Inspect the broken section<\/h3>\n\n\n\n<p>Claude Code reads lines around the error:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sed -n '10,20p' \/etc\/nginx\/nginx.conf\"<\/code><\/pre>\n\n\n\n<p>The output reveals the missing semicolon on the <code>worker_connections<\/code> directive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>include \/usr\/share\/nginx\/modules\/*.conf;\n\nevents {\n    worker_connections 1024\n}\n\nhttp {\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '<\/code><\/pre>\n\n\n\n<p>Line 14 has <code>worker_connections 1024<\/code> without the required trailing semicolon. The closing brace on line 15 becomes &#8220;unexpected&#8221; because Nginx is still looking for the semicolon to terminate the directive.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Fix, re-validate, restart<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo sed -i 's\/worker_connections 1024$\/worker_connections 1024;\/' \/etc\/nginx\/nginx.conf\"<\/code><\/pre>\n\n\n\n<p>Re-validate passes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo nginx -t\"<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>nginx: the configuration file \/etc\/nginx\/nginx.conf syntax is ok\nnginx: configuration file \/etc\/nginx\/nginx.conf test is successful<\/code><\/pre>\n\n\n\n<p>Restart and confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo systemctl restart nginx && systemctl is-active nginx\"<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>active<\/code><\/pre>\n\n\n\n<p>HTTPS verification confirms the site is back:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/2 200\nserver: nginx\/1.26.3<\/code><\/pre>\n\n\n\n<p>The entire sequence, from &#8220;it&#8217;s broken&#8221; to &#8220;it&#8217;s fixed and verified,&#8221; took about 30 seconds. Claude Code followed the same diagnostic path an experienced sysadmin would: check status, validate config, read the error, inspect the file, fix it, re-validate, restart. The difference is speed. For a full reference on managing services, see the <a href=\"https:\/\/computingforgeeks.com\/systemctl-commands-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">systemctl commands guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Multi-Server Health Check<\/h2>\n\n\n\n<p>When your monitoring is down (or you don&#8217;t have monitoring yet), Claude Code can SSH into every server you own and build a health report. This demo ran against three real servers: a Rocky Linux 10 VM, and two Proxmox hypervisors running Debian 12.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SSH into these 3 servers and give me a health report for each:\n- 10.0.1.50 (user: rocky)\n- 10.0.1.10 (user: root)\n- 10.0.1.11 (user: root)\n\nFor each server check: OS version, kernel, uptime, disk usage (flag\nanything over 50%), memory, top 5 CPU processes, any failed systemd\nservices, listening ports, and pending updates.<\/code><\/pre>\n\n\n\n<p>Claude Code SSHes into each server sequentially and aggregates the results. Here is the real output from three production systems:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Server 1: 10.0.1.50 (Rocky Linux 10.1)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>OS:      Rocky Linux 10.1 (Red Quartz)\nKernel:  6.12.0-124.8.1.el10_1.x86_64\nUptime:  5 minutes\n\nDisk:    \/dev\/vda4  19G  1.4G  18G  8% \/        \u2713 OK\n\nMemory:  347Mi used \/ 1.7Gi total (20%)\nSwap:    0B used \/ 0B total\n\nTop CPU:\n  rocky     12.9%  systemd (user session)\n  root       0.8%  systemd (PID 1)\n  root       0.5%  sshd\n\nFailed Services: none\n\nPorts: 22 (SSH), 80 (HTTP), 443 (HTTPS), 9090 (Cockpit\/systemd)\n\nPending Updates: 81 packages (including kernel, curl, glibc security fixes)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Server 2: 10.0.1.10 (Debian 12, Proxmox pve01)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>OS:      Debian GNU\/Linux 12 (bookworm)\nKernel:  6.8.12-13-pve\nUptime:  1 week, 4 days, 19 hours\n\nDisk:    \/dev\/mapper\/pve-root  70G  39G  32G  56% \/  \u26a0 ABOVE 50%\n\nMemory:  5.6Gi used \/ 62Gi total (9%)\nSwap:    0B used \/ 8.0Gi total\n\nTop CPU:\n  root      55.5%  systemd\n  root       3.2%  sshd\n  root       2.5%  kvm (virtual machine)\n  root       1.3%  tailscaled\n\nFailed Services:\n  \u26a0 zfs-import-scan.service - Import ZFS pools by device scanning (FAILED)\n\nPorts: 22, 25 (SMTP localhost), 85 (Proxmox proxy), 111 (RPC)\n\nPending Updates: 138 packages<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Server 3: 10.0.1.11 (Debian 12, Proxmox pve02)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>OS:      Debian GNU\/Linux 12 (bookworm)\nKernel:  6.8.12-13-pve\nUptime:  1 week, 6 days, 2 hours\n\nDisk:    \/dev\/mapper\/pve-root  68G  35G  34G  52% \/  \u26a0 ABOVE 50%\n\nMemory:  13Gi used \/ 62Gi total (21%)\nSwap:    0B used \/ 8.0Gi total\n\nTop CPU:\n  root      25.7%  kvm (VM workload)\n  root      23.9%  kvm (VM workload)\n  root      23.9%  kvm (VM workload)\n  root       0.9%  kvm (VM workload)\n\nFailed Services: none\n\nPorts: 22, 25 (localhost), 85, 111, 3128 (Squid proxy), 8006 (Proxmox UI)\n\nPending Updates: 132 packages<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">What Claude Code flagged<\/h3>\n\n\n\n<p>Three findings that matter:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>pve01 has a failed ZFS service<\/strong> (<code>zfs-import-scan.service<\/code>). This could mean a ZFS pool failed to import on boot, which risks data loss if not investigated<\/li>\n\n<li><strong>Both Proxmox hosts have 130+ pending updates.<\/strong> Security patches (kernel, glibc, curl) are in the queue. These should be applied during a maintenance window<\/li>\n\n<li><strong>Both Proxmox root partitions are above 50%.<\/strong> Not critical yet, but worth monitoring. Old kernel packages and log files are the usual culprits<\/li>\n<\/ul>\n\n\n\n<p>This is the value of conversational infrastructure checks. A raw <code>df -h<\/code> tells you disk is at 56%. Claude Code connects that to the pending updates and suggests cleaning old kernels to free space. You can follow up: &#8220;clean old kernels on pve01 and apply the security updates&#8221; and Claude Code handles both.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Automate Routine Maintenance<\/h2>\n\n\n\n<p>After the health check, the natural follow-up is fixing what it found. Here are real maintenance tasks Claude Code ran on the Rocky VM.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Check SSL certificate expiry<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>Check when the SSL certificate on 10.0.1.50 expires and whether\nauto-renewal is configured.<\/code><\/pre>\n\n\n\n<p>Claude Code checks the certificate and certbot config:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo certbot certificates\"<\/code><\/pre>\n\n\n\n<p>The output shows the full certificate status:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Found the following certs:\n  Certificate Name: sshdemo.computingforgeeks.com\n    Serial Number: 598c82bccd6f11aa439a5823b430c3591b8\n    Key Type: ECDSA\n    Domains: sshdemo.computingforgeeks.com\n    Expiry Date: 2026-06-25 22:12:05+00:00 (VALID: 89 days)\n    Certificate Path: \/etc\/letsencrypt\/live\/sshdemo.computingforgeeks.com\/fullchain.pem\n    Private Key Path: \/etc\/letsencrypt\/live\/sshdemo.computingforgeeks.com\/privkey.pem<\/code><\/pre>\n\n\n\n<p>89 days until expiry. Certbot installs a systemd timer for auto-renewal by default, so no manual intervention needed unless the timer is disabled.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Audit security updates<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>Show me the security updates available on 10.0.1.50. Don't install\nthem yet, just list what's pending.<\/code><\/pre>\n\n\n\n<p>Claude Code runs the security check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo dnf check-update --security\"<\/code><\/pre>\n\n\n\n<p>The output shows real security patches waiting:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>binutils.x86_64                    2.41-58.el10_1.2        baseos\ncurl.x86_64                        8.12.1-2.el10_1.2       baseos\nglibc.x86_64                       2.39-58.el10_1.7        baseos\ngnutls.x86_64                      3.8.10-3.el10_1         baseos\nkernel-core.x86_64                 6.12.0-124.40.1.el10_1  baseos\nlibcurl.x86_64                     8.12.1-2.el10_1.2       baseos\nopenssl-libs.x86_64                3.2.4-1.el10_1          baseos\nsystemd.x86_64                     257.4-3.el10_1.2        baseos<\/code><\/pre>\n\n\n\n<p>Kernel, glibc, curl, openssl, and systemd all have security patches pending. In a real scenario, you&#8217;d tell Claude Code &#8220;apply the security updates and schedule a reboot for tonight&#8221; and it would run <code>dnf update --security -y<\/code> followed by scheduling a reboot.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Audit open ports<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>Audit the open ports on 10.0.1.50. Identify what process owns each\nport and flag anything unexpected.<\/code><\/pre>\n\n\n\n<p>Claude Code runs the port audit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh rocky@10.0.1.50 \"sudo ss -tlnp\"<\/code><\/pre>\n\n\n\n<p>Port ownership identified:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>State  Local Address:Port  Process\nLISTEN 0.0.0.0:22          sshd\nLISTEN 0.0.0.0:80          nginx (master)\nLISTEN 0.0.0.0:443         nginx (master)\nLISTEN *:9090              systemd (PID 1)<\/code><\/pre>\n\n\n\n<p>Claude Code identifies port 9090 as <code>systemd<\/code> (PID 1), which is the Cockpit web console socket. On Rocky Linux 10, Cockpit is enabled by default. If you&#8217;re not using it, Claude Code can disable it: <code>sudo systemctl disable --now cockpit.socket<\/code>. Knowing what&#8217;s listening on your servers is the first step of any hardening exercise. The <a href=\"https:\/\/computingforgeeks.com\/getting-started-with-firewalld-rhel-centos\/\" target=\"_blank\" rel=\"noreferrer noopener\">firewalld configuration guide<\/a> covers locking down ports on RHEL systems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">When to Use Claude Code vs Ansible<\/h2>\n\n\n\n<p>After running these demos, the natural question is: why not just write an Ansible playbook?<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Scenario<\/th><th>Claude Code<\/th><th>Ansible<\/th><\/tr><\/thead><tbody><tr><td>Ad-hoc health check on 3 servers<\/td><td>Best choice (30 seconds, no setup)<\/td><td>Overkill (inventory, playbook, run)<\/td><\/tr><tr><td>Debug a broken service at 2 AM<\/td><td>Best choice (conversational, adaptive)<\/td><td>Can&#8217;t adapt to unknown problems<\/td><\/tr><tr><td>One-time server provisioning<\/td><td>Good (fast for unique setups)<\/td><td>Better if you&#8217;ll repeat it<\/td><\/tr><tr><td>Deploy same config to 50 servers<\/td><td>Wrong tool (no parallelism at scale)<\/td><td>Best choice (designed for fleets)<\/td><\/tr><tr><td>Repeatable, auditable deployments<\/td><td>Wrong tool (no playbook to review)<\/td><td>Best choice (version-controlled YAML)<\/td><\/tr><tr><td>Convert a shell script to IaC<\/td><td>Best choice (generates the Ansible role)<\/td><td>The output, not the tool<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Claude Code is best for tasks you do once or twice: investigating, prototyping, debugging. Ansible is best for tasks you do repeatedly across many servers. The sweet spot: use Claude Code to generate and test the Ansible playbook, then run the playbook directly going forward.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tips for SSH Workflows<\/h2>\n\n\n\n<p>After running dozens of SSH sessions through Claude Code, a few patterns emerged that save time.<\/p>\n\n\n\n<p><strong>Be specific about the SSH user.<\/strong> Claude Code defaults to your local username. If your servers use <code>rocky<\/code>, <code>ubuntu<\/code>, or <code>root<\/code>, include it in the prompt: &#8220;SSH into 10.0.1.5 as rocky.&#8221; This avoids permission errors and retries.<\/p>\n\n\n\n<p><strong>Give context about the server&#8217;s role.<\/strong> &#8220;Check the database server at 10.0.1.5&#8221; tells Claude Code to look for PostgreSQL\/MySQL processes, check tablespace usage, and verify replication. &#8220;Check the web server&#8221; tells it to look for Nginx\/Apache, check SSL certs, and test HTTP responses. The role hint focuses the investigation.<\/p>\n\n\n\n<p><strong>Ask for a report, not individual commands.<\/strong> &#8220;Give me a health report&#8221; produces structured output. &#8220;Run df -h&#8221; produces raw output. The report format is more useful because Claude Code adds analysis: flagging high usage, identifying unknown services, comparing against best practices.<\/p>\n\n\n\n<p><strong>Use follow-up prompts.<\/strong> After a health check, ask &#8220;fix the issues you found&#8221; or &#8220;apply the security updates.&#8221; Claude Code remembers the full context from earlier in the conversation, including which servers had problems and what the errors were. No need to repeat details.<\/p>\n\n\n\n<p>For the complete list of SSH options and patterns, the <a href=\"https:\/\/computingforgeeks.com\/ssh-commands-cheat-sheet-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">SSH commands cheat sheet<\/a> is a useful companion reference.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Claude Code Cannot Do Over SSH<\/h2>\n\n\n\n<p>A few hard limitations to know before you rely on it:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>No interactive sessions.<\/strong> Claude Code runs individual commands via <code>ssh user@host \"command\"<\/code>. It cannot open vi, use top interactively, or respond to password prompts mid-session. Every command must complete and return output non-interactively<\/li>\n\n<li><strong>No persistent state between commands.<\/strong> Each SSH invocation starts a new session. Environment variables set in one command are gone in the next. Claude Code works around this by chaining commands with <code>&&<\/code> or writing to files, but it&#8217;s less elegant than a real terminal session<\/li>\n\n<li><strong>No port forwarding or tunnels.<\/strong> Claude Code cannot set up SSH tunnels for database access or forward ports from remote servers. Use manual SSH for that<\/li>\n\n<li><strong>Large output gets truncated.<\/strong> If a command produces thousands of lines (full log dumps, package listings), Claude Code may not see all of it. Use <code>tail -n 50<\/code> or <code>grep<\/code> to filter before piping to Claude Code<\/li>\n<\/ul>\n\n\n\n<p>These limitations are architectural, not bugs. Claude Code trades interactive flexibility for contextual intelligence: it understands what the output means and acts on it, which a regular SSH session cannot do.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Part of the Claude Code for DevOps Series<\/h2>\n\n\n\n<p>This is the SSH spoke in a series of hands-on guides. Each article covers a different infrastructure tool with the same approach: real prompts, real servers, real output.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/computingforgeeks.com\/claude-code-devops-engineers\/\" target=\"_blank\" rel=\"noreferrer noopener\">Set Up Claude Code for DevOps Engineers<\/a> (pillar guide with setup, safety rules, permissions)<\/li>\n\n<li><a href=\"https:\/\/computingforgeeks.com\/claude-code-terraform-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Claude Code + Terraform<\/strong><\/a>: generate modules, deploy real infrastructure, import resources<\/li>\n\n<li><a href=\"https:\/\/computingforgeeks.com\/claude-code-ansible-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Claude Code + Ansible<\/strong><\/a>: generate playbooks, convert scripts to roles, multi-OS testing<\/li>\n\n<li><a href=\"https:\/\/computingforgeeks.com\/claude-code-docker-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Claude Code + Docker<\/strong><\/a>: multi-stage builds, Compose stacks, container debugging<\/li>\n\n<li><a href=\"https:\/\/computingforgeeks.com\/claude-code-kubernetes-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Claude Code + Kubernetes<\/strong><\/a>: manifests, Helm charts, pod log analysis<\/li>\n\n<li><a href=\"https:\/\/computingforgeeks.com\/claude-code-github-actions-infrastructure\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Claude Code + GitHub Actions<\/strong><\/a>: automated PR review, infrastructure validation<\/li>\n<\/ul>\n\n\n\n<p>The <a href=\"https:\/\/computingforgeeks.com\/claude-code-cheat-sheet\/\" target=\"_blank\" rel=\"noreferrer noopener\">Claude Code cheat sheet<\/a> covers every command and shortcut if you want a quick reference while working through these demos.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Every sysadmin SSHes into servers daily. Claude Code can do it too, and it remembers context across commands. You tell it &#8220;the web server on 10.0.1.5 is down, fix it&#8221; and it checks the service status, reads the error log, identifies the config problem, patches it, re-validates, and restarts. No scripting. No copying error messages &#8230; <a title=\"Manage Servers with Claude Code via SSH\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/claude-code-ssh-server-management\/\" aria-label=\"Read more about Manage Servers with Claude Code via SSH\">Read more<\/a><\/p>\n","protected":false},"author":3,"featured_media":164927,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[39034,690,299],"tags":[],"class_list":["post-164926","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-dev","category-how-to"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/164926","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=164926"}],"version-history":[{"count":2,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/164926\/revisions"}],"predecessor-version":[{"id":164979,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/164926\/revisions\/164979"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/164927"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=164926"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=164926"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=164926"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}