Ubuntu server configuration via cloud-init and systemd. Use when writing cloud-init YAML, configuring systemd services, setting up nginx, or troubleshooting VM configuration issues.
Scanned 2/10/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: configure-ubuntu
description: Ubuntu server configuration via cloud-init and systemd. Use when writing cloud-init YAML, configuring systemd services, setting up nginx, or troubleshooting VM configuration issues.
---
# Configure Ubuntu Skill
## Purpose
This skill provides guidance for configuring Ubuntu LTS servers via cloud-init automation for any cloud platform (AWS, Azure, GCP, DigitalOcean, etc.).
> **Note:** All examples in this skill currently use **Ubuntu 24.04 LTS**. Update version-specific package names or paths as needed for other LTS releases.
## When to Use This Skill
Use this skill when:
- Writing cloud-init YAML for VM provisioning
- Configuring systemd services
- Setting up nginx reverse proxies
- Troubleshooting cloud-init or systemd issues
- Setting file permissions for service users
## Reference Selection Guide
Choose the appropriate reference based on your task:
| Task | Reference File |
|------|----------------|
| Complete cloud-init configurations | `references/examples/*.yaml` |
| Cloud-init syntax and patterns | `references/cloud-init-patterns.md` |
| systemd service units | `references/systemd-patterns.md` |
| Debugging failures or errors | `references/common-pitfalls.md` |
### Ready-to-Use Examples
| Example | File |
|---------|------|
| SSH bastion host | `references/examples/bastion.yaml` |
| Nginx reverse proxy (HTTP) | `references/examples/proxy-http.yaml` **← default** |
| Nginx reverse proxy (HTTPS, self-signed) | `references/examples/proxy-https.yaml` |
| Nginx reverse proxy (HTTPS, Let's Encrypt) | `references/examples/proxy-https-letsencrypt.yaml` |
| Nginx static file server | `references/examples/static-files.yaml` |
| Python/Flask server | `references/examples/flask.yaml` |
| Node.js server | `references/examples/nodejs.yaml` |
| Java/Spring Boot server | `references/examples/java.yaml` |
| .NET server | `references/examples/dotnet.yaml` |
> **HTTPS Options:**
> - Use `proxy-http.yaml` by default (no SSL)
> - Use `proxy-https.yaml` for self-signed certificates (development/internal)
> - Use `proxy-https-letsencrypt.yaml` for trusted certificates (requires domain name)
### When to Read Multiple References
- **New VM setup:** Start with relevant example, then cloud-init-patterns.md for customization
- **Service won't start:** common-pitfalls.md + systemd-patterns.md
- **SSH access broken:** common-pitfalls.md (Issue #1)
- **Permission denied errors:** common-pitfalls.md (Issues #4, #5, #6)
- **Scheduled tasks:** systemd-patterns.md (Systemd Timers section)
- **Production HTTPS:** proxy-https-letsencrypt.yaml (requires domain name)
- **Static website:** static-files.yaml + proxy-http.yaml (if reverse proxy needed)
---
## Critical Rules (Memorize These)
These rules prevent the most common failures:
1. **NEVER use `users:` directive in cloud-init** — Deletes default SSH user, breaks access
2. **Systemd: NO inline comments** — `After=network.target # comment` fails
3. **Permissions as quoted strings** — Use `'0640'` not `0640` in cloud-init
4. **Wait for cloud-init** — Takes 2-3 minutes; don't deploy until done
5. **Absolute paths in systemd** — Use `/opt/app/venv/bin/python` not `python`
6. **Fix permissions after SCP** — Files copied retain local permissions
---
## Default Choices for Vague Requests
When the request is ambiguous, use these defaults:
| Decision | Default Choice | Rationale |
|----------|----------------|-----------|
| **Application framework** | Flask/Python | Course standard, most examples |
| **Reverse proxy** | HTTP (not HTTPS) | Simpler, use `proxy-http.yaml` |
| **Application port** | 5001 | Avoids conflict with common ports |
| **Proxy port** | 80 (HTTP) or 443 (HTTPS) | Standard web ports |
| **App directory** | `/opt/<app-name>/` | Standard for third-party apps |
| **Config directory** | `/etc/<app-name>/` | Standard for system config |
| **Service user** | `<app-name>` | Same as app, no login shell |
| **Ownership pattern** | `deployuser:servicegroup` | Deploy user writes, service reads |
| **Directory permissions** | 775 | Group can write (venv installs) |
| **Config file permissions** | 640 | Owner writes, group reads |
| **WSGI server** | Gunicorn | Production-ready, simple config |
### Example Defaults
**"Set up a web application server"** → Use:
- `examples/flask.yaml` for application VM
- `examples/proxy-http.yaml` for proxy VM
- Port 5001 for Flask, port 80 for nginx
**"Configure nginx"** → Use:
- `examples/proxy-http.yaml` (reverse proxy, not static files)
- Unless HTTPS explicitly requested, then `proxy-https.yaml`
**"Set up a secure bastion"** → Use:
- `examples/bastion.yaml` (includes UFW + fail2ban)
---
## Environment Variables Pattern
**This is the standard way to handle environment variables (database credentials, API keys, connection strings) on the server.**
### Directory Structure
```
/etc/<app-name>/
└── environment # All environment variables for the application
```
### Setup in Cloud-init
```yaml
runcmd:
# Create config directory owned by root, readable by app group
- mkdir -p /etc/myapp
- chown root:myapp /etc/myapp
- chmod 750 /etc/myapp
# Create environment file with restricted permissions
- touch /etc/myapp/environment
- chown root:myapp /etc/myapp/environment
- chmod 640 /etc/myapp/environment
```
### Reference from systemd
```ini
[Service]
EnvironmentFile=/etc/myapp/environment
```
### Environment File Format
```bash
# /etc/myapp/environment
DATABASE_URL=postgresql://user:password@host:5432/dbname
SECRET_KEY=your-secret-key-here
API_KEY=external-service-api-key
DEBUG=false
```
### Why This Pattern?
| Concern | Solution |
|---------|----------|
| **Security** | File owned by `root`, only readable by app group (mode `640`) |
| **Separation** | Config in `/etc/`, code in `/opt/` — different concerns, different locations |
| **Deployment** | Environment file can be updated without redeploying code |
| **systemd integration** | `EnvironmentFile=` loads variables automatically |
---
## Quick Reference Tables
### Default SSH Users by Cloud Provider
| Provider | Default User |
|----------|--------------|
| AWS (Amazon Linux) | `ec2-user` |
| AWS (Ubuntu) | `ubuntu` |
| Azure | `azureuser` |
| GCP | Your Google account username |
| DigitalOcean | `root` |
| Linode | `root` |
### File Permission Patterns
| Purpose | Owner:Group | Mode |
|---------|-------------|------|
| Application code | deployuser:appgroup | 640 |
| Application directory | deployuser:appgroup | 775 |
| Config with secrets | root:appgroup | 640 |
| Config directory | root:appgroup | 750 |
| systemd service files | root:root | 644 |
### Common systemctl Commands
```bash
sudo systemctl daemon-reload # After editing service files
sudo systemctl enable myapp # Start on boot
sudo systemctl start myapp # Start now
sudo systemctl status myapp # Check status
sudo journalctl -u myapp -f # Follow logs
```
### Cloud-init Verification
```bash
cloud-init status # Check status
cloud-init status --wait # Block until complete
sudo cat /var/log/cloud-init-output.log # View logs
```
---
## Reference Summaries
### examples/ directory
Ready-to-use cloud-init YAML files for common server types. Copy and customize for your use case.
### cloud-init-patterns.md
Reference documentation for cloud-init syntax:
- Module execution order
- Package management (`packages`, `package_update`)
- Writing configuration files (`write_files`)
- Running commands (`runcmd`)
- Debugging and lifecycle
### systemd-patterns.md
Reference documentation for systemd services:
- Service unit file structure (`[Unit]`, `[Service]`, `[Install]`)
- Service types (`simple`, `forking`, `oneshot`)
- Environment configuration
- Restart policies and resource limits
- **Systemd timers** (scheduled tasks, replacement for cron)
- Managing and debugging services
### common-pitfalls.md
Prevention checklist and solutions for:
- Cloud-init issues (user deletion, timing)
- systemd issues (inline comments, permissions)
- File permission problems
- SSH and networking issues
No comments yet. Be the first to comment!