How to Execute EC2 User-Data Scripts as Non-Root User for Secure Process Management


4 views

When launching Amazon EC2 instances, user-data scripts execute with root privileges by default. While this provides broad system access, it creates security concerns for long-running processes. The Amazon Linux 2012.03 AMI (and subsequent versions) don't natively support non-root execution of user-data scripts.

Here are three reliable approaches to run processes as non-root users:

#!/bin/bash
# Method 1: Explicit user switching
useradd -m appuser
su - appuser -c "/path/to/your/script.sh"

For production environments, consider using process managers:

#!/bin/bash
# Create systemd service running as non-root
cat > /etc/systemd/system/myapp.service << EOF
[Unit]
Description=My Application
[Service]
User=appuser
ExecStart=/usr/bin/python3 /opt/app/main.py
[Install]
WantedBy=multi-user.target
EOF
systemctl enable myapp

Combine these techniques with AWS IAM roles for secure credential management:

#!/bin/bash
# Create restricted credentials directory
mkdir -p /home/appuser/.aws
chown -R appuser:appuser /home/appuser/.aws
cat > /home/appuser/.aws/config << EOF
[default]
region = us-west-2
credential_process = /usr/bin/aws-credentials-helper
EOF
  • Always set proper file permissions (chmod/chown)
  • Use AWS Systems Manager for secret management
  • Implement least privilege IAM policies
  • Monitor processes with CloudWatch

If encountering permission issues:

# Check process ownership
ps aux | grep your_process
# Verify file permissions
namei -l /path/to/resource

When deploying applications on Amazon EC2 instances through user-data scripts, all commands execute with root privileges by default. While convenient for system configuration, this poses security risks for long-running application processes. The Amazon Linux 2012.03 AMI (and subsequent versions) maintain this behavior for backward compatibility.

The simplest method involves using sudo -u to switch user context:

#!/bin/bash
# Create application user if not exists
id -u appuser &>/dev/null || useradd -m appuser

# Launch process as non-root
sudo -u appuser /path/to/your/application --daemonize

For production environments, consider integrating with systemd or supervisor:

#!/bin/bash
cat > /etc/systemd/system/myapp.service << EOF
[Unit]
Description=My Application
After=network.target

[Service]
User=appuser
Group=appgroup
ExecStart=/usr/bin/python3 /opt/app/main.py
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable myapp
systemctl start myapp

For complex deployments, create an isolated environment:

#!/bin/bash
APP_USER="deployer"
APP_HOME="/opt/application"

useradd -r -m -d "$APP_HOME" -s /bin/bash "$APP_USER"
mkdir -p "$APP_HOME"/{bin,logs,tmp}
chown -R "$APP_USER":"$APP_USER" "$APP_HOME"

# Switch context using su -c
su - "$APP_USER" -c "/opt/application/bin/start_server.sh"

Always follow these practices when downgrading privileges:

  • Set proper directory permissions (750 for sensitive locations)
  • Use chmod to restrict executable permissions
  • Consider implementing Linux capabilities instead of full root
  • Log all privilege transitions for auditing

If processes fail to start:

# Check user permissions
sudo -u appuser id
sudo -u appuser ls -l /path/to/resource

# Verify environment variables
sudo -u appuser env

# Test process startup manually
sudo -u appuser /path/to/binary --dry-run