DEV Community

Richard Kabi
Richard Kabi

Posted on AI-assisted

From One Laptop to HTTPS: Deploying Miren on EC2

What happened

I deployed bin/miren, the Miren runtime binary, from my development environment to an EC2 instance and configured HTTPS access through Caddy.

The objective was more than getting the application to respond to a request. I wanted to move from an application that worked on one laptop to a deployment that could run independently on a real Linux host, recover from unexpected process termination, start automatically after a reboot, and serve traffic over HTTPS.

Getting there exposed several practical challenges: establishing administrative access through AWS Systems Manager (SSM), diagnosing a service that exited immediately after starting, understanding the runtime's privilege requirements, and configuring TLS termination.

1. Establishing access through AWS SSM

Direct SSH access was not convenient in this environment, so I used AWS Systems Manager Session Manager with port forwarding to tunnel SSH traffic.

The forwarding configuration mapped local port 2222 to port 22 on the EC2 instance. This allowed me to connect through localhost:2222 and transfer the binary and configuration files without opening an additional inbound SSH port on the instance.

The command was:

aws ssm start-session \
  --target <instance-id> \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["22"],"localPortNumber":["2222"]}'
Enter fullscreen mode Exit fullscreen mode

With the session running, I could use SSH and SCP through the forwarded port. For example:

ssh -p 2222 <ssh-user>@localhost
Enter fullscreen mode Exit fullscreen mode

And, from another terminal:

scp -P 2222 bin/miren <ssh-user>@localhost:/tmp/miren
Enter fullscreen mode Exit fullscreen mode

This approach requires an appropriately configured SSM-managed instance, the necessary IAM permissions, and a working SSM connectivity path.

It was my first time using SSM port forwarding this way. For this deployment, it provided a convenient alternative to exposing SSH directly or introducing a jump host.

2. Configuring Miren as a systemd service

After transferring the binary, I configured a systemd unit so that Miren could run as a managed service rather than depending on an interactive terminal session.

The important directives included:

  • ExecStart: specifies the executable systemd should run.
  • WorkingDirectory: defines the service's working directory.
  • Restart=always: instructs systemd to restart the service after it exits, subject to systemd's restart and start-rate limits.
  • RestartSec: introduces a delay before restarting the service.
  • WantedBy=multi-user.target: allows the service to be enabled for the normal multi-user boot target.

After creating the unit, the relevant commands were:

sudo systemctl daemon-reload
sudo systemctl enable miren
sudo systemctl start miren
Enter fullscreen mode Exit fullscreen mode

However, the service repeatedly became inactive (dead) shortly after starting.

3. Diagnosing the service that kept exiting

My initial focus was on the systemd configuration, permissions, and service setup. Inspecting the service logs helped narrow down the problem.

I used:

sudo systemctl status miren
sudo journalctl -u miren -n 50 --no-pager
Enter fullscreen mode Exit fullscreen mode

The actual cause was simpler than I expected: I had transferred an outdated build of bin/miren.

After replacing it with the intended binary and correcting its permissions and ownership, the service remained running.

This was a useful reminder that an application startup failure is not necessarily a systemd problem. The executable itself, its version, its architecture, its permissions, and its runtime dependencies all need to be considered.

A service manager can restart a process, but it cannot correct a broken or outdated application binary.

4. Understanding the containerd privilege requirement

Another challenge emerged when I considered running Miren under a dedicated, non-root service account.

A non-root service account is generally preferable because it limits the impact of a compromised process. However, Miren interacts with containerd to manage containers, and its runtime requirements introduced a privilege constraint in this deployment.

This forced me to reconsider my original plan.

It is important to distinguish between three things:

  1. The privileges required by the containerd daemon itself.
  2. The permissions required to access the containerd socket.
  3. The privileges required by the Miren process to perform its operations.

Access to a privileged containerd socket can itself grant substantial control over the host, so giving a process socket access is not automatically equivalent to running it with minimal privileges.

My experience showed me that least privilege must be applied with an understanding of the software's actual requirements. If a component genuinely requires elevated privileges, the next step is to identify the precise requirement and apply appropriate compensating controls rather than assuming that a non-root configuration will work.

I would verify whether Miren can operate with restricted socket access or a more limited service account before concluding that running the entire service as root is unavoidable.

5. Configuring HTTPS with Caddy and Cloudflare DNS

Once the application was running, I configured Caddy as a TLS-terminating reverse proxy and configured Cloudflare DNS to point the application domain to the EC2 instance.

The intended request flow was:

Client
  |
  | HTTPS
  v
Application domain
  |
  v
EC2 instance
  |
  v
Caddy (TLS termination)
  |
  v
Miren runtime
Enter fullscreen mode Exit fullscreen mode

This illustrates the main components; the actual connection between Caddy and Miren depends on the upstream address and protocol configured in Caddy.

Caddy can obtain and renew publicly trusted TLS certificates automatically when its domain, DNS, network accessibility, and configuration satisfy the relevant requirements. Cloudflare DNS provides name resolution, while Cloudflare's optional proxy service introduces an additional layer if enabled.

An important distinction is that HTTPS to Caddy does not automatically mean that every connection between the client, any intermediary proxy, and the application process uses TLS. The security of each connection depends on the actual request path and its configuration.

The objective was to make the application accessible through its domain over HTTPS rather than relying on plain HTTP.

The concepts behind the deployment

1. A successful request is not proof of a reliable deployment

An application responding to curl once proves that it can serve a request under those conditions. It does not prove that it will recover from a failure or start after the host restarts.

I treated deployment reliability as a set of separate concerns:

  • Availability: can the application respond to requests?
  • Process recovery: does the service recover after an unexpected process exit?
  • Boot persistence: does the service start automatically after a host reboot?
  • Transport security: is traffic protected appropriately in transit?

Each concern requires its own verification.

2. systemd provides process supervision, not application correctness

The Restart=always directive helps systemd recover from unexpected process exits. However, restart behavior is subject to systemd's configuration, including start-rate limiting and restart delays.

A restart policy does not guarantee that an application will become healthy. A service can repeatedly crash, restart, and fail again.

The important distinction is between a process being started and an application being ready to serve requests.

For production systems, process supervision should be combined with application-level health checks, useful logs, and monitoring appropriate to the workload.

3. Least privilege requires understanding runtime dependencies

Running services under dedicated accounts reduces unnecessary privileges, but the account must still have the permissions required to perform its intended work.

When containers are involved, permissions around the container runtime and its control socket deserve particular attention. A service should not receive unrestricted host-level access simply because it needs to manage containers.

The correct approach is to establish the minimum required permissions, test the resulting configuration, and document any privileges that cannot safely be removed.

4. HTTPS protects connections, but the entire request path matters

Plain HTTP does not provide transport encryption or server authentication. HTTPS protects the connection between the client and the TLS endpoint, assuming the certificate is valid and the client verifies it correctly.

When a reverse proxy terminates TLS, the connection from the proxy to the upstream application must be considered separately.

For a deployment using Cloudflare and an origin proxy, I need to understand which connections are encrypted, how the origin is authenticated, and which ports are publicly reachable.

Practical commands and troubleshooting notes

Inspect a systemd service

sudo systemctl status miren
sudo journalctl -u miren -n 50 --no-pager
sudo systemctl is-enabled miren
sudo systemctl is-active miren
Enter fullscreen mode Exit fullscreen mode

These commands help distinguish service state, startup errors, boot configuration, and current activity.

Check the executable

ls -l /opt/miren/bin/miren
file /opt/miren/bin/miren
Enter fullscreen mode Exit fullscreen mode

The first command checks file permissions and ownership. The second helps identify the executable's file format and architecture.

Where possible, I should also verify the build version or commit identifier against the intended release before transferring a binary.

Verify HTTPS

curl -Iv https://<your-domain>
Enter fullscreen mode Exit fullscreen mode

This provides information about the HTTPS connection and response. It should be supplemented by checking the certificate's hostname, validity period, and trust chain.

A successful HTTP response alone does not prove that every part of the deployment is configured securely.

What I would do differently

1. Verify the binary before investigating systemd

I spent time examining service configuration when the actual problem was an outdated binary.

Before deployment, I would verify the executable's version or build identifier, architecture, permissions, and expected runtime dependencies. I would also confirm that the file transferred to the server matches the intended artifact.

2. Investigate runtime requirements before designing service permissions

I would identify Miren's requirements for containerd access, socket permissions, and privileged operations before finalizing the service account.

This would allow me to design the security model around verified requirements instead of discovering constraints after implementing a restrictive configuration.

3. Follow a consistent first-deployment checklist

For future deployments, I would use this sequence:

  1. Verify the target host and operating system.
  2. Confirm the binary and its dependencies.
  3. Run the application manually in a controlled environment where appropriate.
  4. Create and validate the systemd unit.
  5. Reload systemd and start the service.
  6. Inspect service status and logs immediately.
  7. Verify application functionality.
  8. Test recovery from unexpected process termination.
  9. Reboot the host and verify automatic service startup.
  10. Configure HTTPS and verify the certificate and request path.
  11. Review exposed ports, service privileges, and logs.

This order should make failures easier to isolate and reduce time spent debugging unrelated layers.

Key takeaways

This deployment reinforced several lessons:

  • A deployment is more than a successful application startup.
  • Stale binaries can produce misleading debugging sessions.
  • systemd can supervise a process, but it cannot guarantee application health.
  • Container runtime permissions must be understood before designing a least-privilege service configuration.
  • HTTPS must be evaluated across the actual request path, especially when reverse proxies or CDN services are involved.
  • Reliability claims should be backed by observable verification results.

The most important lesson was that deploying software requires more than making it work once. It requires understanding the application, its dependencies, its privileges, its failure modes, and the evidence needed to establish that the deployment is behaving as intended.

References


This write-up documents my learning from a real deployment. Before publishing, I will confirm that the details are permitted to be shared and add the actual verification results from the deployment.

Top comments (0)