Enable or Disable Services on an Instance

Overview

Every node in a deployment registers a set of roles, and those roles decide which services run on it. Whether a node gets a role comes down to CloudFormation parameters that mark each service as enabled or disabled. Some of these parameters are exposed to customers. For example, changing the cache application to varnish will toggle the varnish role on a server. Another example is on clusters, where choosing to deploy the database in RDS means the database role won't be enabled on any of the nodes. The rest of the parameters aren't exposed, so changing them is up to us.

In this guide, we'll take a LEMP AIO server and turn off everything except redis-sessions. This is a realistic use case where a customer wants to host a node app themselves but still wants redis for their sessions. By the end, everything else on the box will be off, including opensearch, rabbitmq, nginx, php, etc.

How It Works

Since these parameters aren't exposed to the customer, they have to be updated directly on the parent stack. You'll log into the AWS account via SSO and perform a direct update with new parameter values. There are no template changes involved, just parameter updates.

Once the stack update starts, the new values propagate down to the instance. The cfn-hup hooks running on the instance detect the change and update the node's roles via /opt/jrc/sbin/assume-roles. After that, the boot.d hooks run and align the state of each service to the roles, enabling or disabling them accordingly. And since the change lives on the parent stack, it will stick through reboots and future updates.

Getting Started

Before starting, grab the AWS account ID from the organization's settings page and the deployment ID from the deployment's settings page. You'll also want shell access to the instance so you can watch the change happen and verify it after.

Create a LEMP deployment and wait for provisioning to finish. Once it's up, whitelist your IP and add your SSH key through the AutoPilot dashboard, then SSH in. The MOTD shows all the roles the node currently has:

raffi@m3 ~ % ssh jrc-2cff-zs91@98.95.243.141

   ___| _ \  __|   Image:    LEMP (JetRails Cloud)
     | |_ / (      Leader:   Yes
  __/ _| _\ ___|   Role(s):  database, jump, queue, redis-cache, redis-session,
                             search, storage, varnish, web

Now log into the production SSO account and assume the ProdAutoPilotSupportLevelTwo role (or higher) in the correct AWS account. Open CloudFormation and find the parent stack for the deployment. You can untoggle the View nested switch to make it easier to find. Click Update stack, choose Make a direct update, and keep the existing template. On the parameters page, look for the parameters that end in Enabled. Setting one to no turns the service off and setting it to yes turns it on. For our example, we set all of them to no except the one for redis-sessions. Continue through the wizard and submit the update.

Monitor Process

While the stack is updating, tail the cfn-hup log on the instance. You'll see the change-roles-hook get triggered once the new parameter values reach the instance:

jrc-2cff-zs91@jump-leader ~ $ tail -f /var/log/cfn-hup.log
2026-07-13 16:58:36,389 [DEBUG] No change in path Resources.Ec2Instance.Metadata.AWS::CloudFormation::Hup for hook set-variables-hook
2026-07-13 17:00:03,390 [INFO] cfn-hup processing is alive.
2026-07-13 17:00:03,390 [DEBUG] Describing resource Ec2Instance in stack arn:aws:cloudformation:us-east-1:426280914186:stack/jrc-8a4cb6ea-95a6-4c5e-9584-eae56fc86bee-LayerJrcJump-18XTKZSG3E35Y/7f02a9c0-7ed9-11f1-9b9e-0affc7f2942d
2026-07-13 17:00:03,478 [DEBUG] Skipping resource Ec2Instance in arn:aws:cloudformation:us-east-1:426280914186:stack/jrc-8a4cb6ea-95a6-4c5e-9584-eae56fc86bee-LayerJrcJump-18XTKZSG3E35Y/7f02a9c0-7ed9-11f1-9b9e-0affc7f2942d as it is in status UPDATE_IN_PROGRESS
2026-07-13 17:00:03,478 [DEBUG] Skipping resource Ec2Instance in arn:aws:cloudformation:us-east-1:426280914186:stack/jrc-8a4cb6ea-95a6-4c5e-9584-eae56fc86bee-LayerJrcJump-18XTKZSG3E35Y/7f02a9c0-7ed9-11f1-9b9e-0affc7f2942d as it is in status UPDATE_IN_PROGRESS
2026-07-13 17:01:23,478 [DEBUG] Describing resource Ec2Instance in stack arn:aws:cloudformation:us-east-1:426280914186:stack/jrc-8a4cb6ea-95a6-4c5e-9584-eae56fc86bee-LayerJrcJump-18XTKZSG3E35Y/7f02a9c0-7ed9-11f1-9b9e-0affc7f2942d
2026-07-13 17:01:23,565 [INFO] Data has changed from previous state; action for change-roles-hook will be run
2026-07-13 17:01:23,566 [INFO] Running action for change-roles-hook
2026-07-13 17:01:44,611 [DEBUG] Action for change-roles-hook output: <None>
2026-07-13 17:01:44,611 [DEBUG] No change in path Resources.Ec2Instance.Metadata.AWS::CloudFormation::Hup for hook set-variables-hook

Verify Changes

Once that hook finishes, the node's roles are updated. Running cluster list shows that the only roles left are jump, redis-session, and storage:

jrc-2cff-zs91@jump-leader ~ $ cluster list

   NODE                 PRIVATE-IP       LAST-CHECKED-IN    HEALTHY  LEADER  ROLES
 ► i-052be153e52f16000  10.10.22.9       07/13/26 17:03:01  Yes      Yes     jump, redis-session, storage

The services that run in docker are gone too, and only the redis-session container is left:

jrc-2cff-zs91@jump-leader ~ $ sudo docker ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED          STATUS          PORTS                    NAMES
39fb3bcdcc4b   valkey/valkey:9.1-alpine-custom   "docker-entrypoint.s…"   21 minutes ago   Up 21 minutes   0.0.0.0:6379->6379/tcp   redis-session

And the services that run on the host, like nginx and php-fpm, are now inactive:

jrc-2cff-zs91@jump-leader ~ $ sudo systemctl is-active nginx
inactive
jrc-2cff-zs91@jump-leader ~ $ sudo systemctl is-active php-fpm
inactive
jrc-2cff-zs91@jump-leader ~ $

Verify Reachability

Finally, we can prove that traffic bypasses nginx altogether. Since nginx is no longer running, nothing is listening on port 80, so we can start a quick python server on it:

jrc-2cff-zs91@jump-leader ~ $ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.25.71 - - [13/Jul/2026 17:41:07] "GET / HTTP/1.1" 200 -

Hit the preview domain in your browser and you'll see the request come through and get served by the python server. A node app would behave the same way since it runs its own listener on the port.