How to Deploy ElasticMQ as an AWS SQS Alternative

ElasticMQ is an in-memory message queue system with an Amazon Simple Queue Service (SQS)-compatible interface. It provides a lightweight, fast alternative to AWS SQS for local development, testing, and production workloads where cloud dependency is undesirable. ElasticMQ supports standard and First-In-First-Out (FIFO) queues, dead-letter queues, message visibility timeouts, and optional persistence to survive restarts.
This article explains how to deploy ElasticMQ as a self-hosted alternative to AWS SQS. It covers a Docker-based installation, queue configuration, dead-letter queue setup, the web user interface (UI), message persistence, and migration strategies from AWS SQS.
Key Differences Between ElasticMQ and AWS SQS
AWS SQS is a fully managed message queue service tightly integrated with the AWS ecosystem. ElasticMQ offers SQS API compatibility with infrastructure independence and simplified deployment.
- Deployment Model: AWS SQS runs as a managed service with automatic scaling across multiple Availability Zones. ElasticMQ deploys on any infrastructure you control, including on-premises servers, virtual machines (VMs), or Kubernetes clusters.
- API Compatibility: ElasticMQ implements the SQS query API, enabling existing AWS SDK code to work with minimal changes. Point your SDK client to the ElasticMQ endpoint instead of AWS.
- Pricing: AWS SQS charges per request with additional costs for data transfer. ElasticMQ is open-source with no licensing costs beyond infrastructure.
- Persistence: AWS SQS stores messages redundantly across multiple data centers. ElasticMQ runs in-memory by default but supports optional persistence to an H2 database.
- Feature Parity: AWS SQS includes features like server-side encryption, Virtual Private Cloud (VPC) endpoints, and CloudWatch integration. ElasticMQ focuses on core queue functionality without AWS-specific integrations.
Understanding ElasticMQ Architecture
ElasticMQ maps directly to AWS SQS concepts.
| AWS SQS | ElasticMQ | Description |
|---|---|---|
| Standard Queues | Standard Queues | At-least-once delivery with best-effort ordering. |
| FIFO Queues | FIFO Queues | Exactly-once processing with strict message ordering. |
| Dead-Letter Queues | Dead-Letter Queues | Stores messages that exceed the maximum receive count. |
| Visibility Timeout | Visibility Timeout | Time a message is hidden after being received. |
| Message Delay | Delay Seconds | Time before a message becomes available for consumption. |
| Long Polling | Receive Message Wait | Reduces empty responses by waiting for messages. |
The ElasticMQ architecture consists of:
- REST-SQS Interface: HTTP API on port 9324 that accepts SQS-compatible requests. Any AWS SDK or CLI configured to use this endpoint works without code changes.
- Queue Storage: In-memory storage by default for maximum performance. Optional H2 database persistence stores queues and messages to disk.
- Web UI: Browser-based interface on port 3000 (via separate
elasticmq-uicontainer) for viewing queue statistics and message counts in real time. - Configuration System: Human-Optimized Config Object Notation (HOCON)-based configuration files define queues, dead-letter policies, and server settings at startup.
Prerequisites
Before you begin, you need to:
- Have access to a Linux-based server as a non-root user with sudo privileges.
- Install Docker and Docker Compose.
- Register a domain name and create a DNS A record pointing it to your server's public IP address, for the REST API endpoint. This domain requests a TLS certificate from Let's Encrypt through Traefik. See How to Manage Domain DNS Records if you use Vultr DNS.
- Create a second DNS A record on a separate subdomain (for example,
ui.DOMAIN-NAME) pointing to the same server, for the Web UI. The UI application serves its static assets from absolute root paths, so it cannot share a path prefix on the same domain as the REST API.
Deploy ElasticMQ with Docker
Docker provides the fastest path to a running ElasticMQ instance. The native image built with GraalVM starts in milliseconds and uses minimal memory. Traefik fronts the deployment as a reverse proxy and TLS terminator, obtaining a Let's Encrypt certificate for your domain. It routes to the ElasticMQ containers directly over the internal Docker network using labels.
Create the project directory with subdirectories for data persistence and TLS certificate storage.
console$ mkdir -p ~/elasticmq/{data,letsencrypt}
data: Stores the H2 database files for message persistence.letsencrypt: Stores the Let's Encrypt certificate Traefik obtains for your domain.
Switch to the project directory.
console$ cd ~/elasticmq
Create the ElasticMQ configuration file.
console$ nano elasticmq.conf
Add the following configuration to define the server settings and pre-create queues, replacing
DOMAIN-NAMEwith the domain you pointed at your server in the Prerequisites section.iniinclude classpath("application.conf") node-address { protocol = https host = DOMAIN-NAME port = 443 context-path = "" } rest-sqs { enabled = true bind-port = 9324 bind-hostname = "0.0.0.0" sqs-limits = strict } queues { default-queue { defaultVisibilityTimeout = 30 seconds delay = 0 seconds receiveMessageWait = 0 seconds } } aws { region = us-east-1 accountId = 000000000000 }
Save and close the file.
In this configuration:
node-address: Defines the external URL used in queue URLs returned by the API. Setting this to your domain over HTTPS ensures queue URLs work through the Traefik front end you configure next.rest-sqs: Configures the SQS-compatible API endpoint.bind-hostnamestays at0.0.0.0so Traefik can reach the container over the internal Docker network.queues: Pre-creates queues at startup with specified attributes.aws: Sets the region and account ID used in queue URLs for SQS API compatibility.
Generate a password hash to protect the Web UI with HTTP basic authentication, replacing
UI-PASSWORDwith a password of your choice.console$ openssl passwd -apr1 UI-PASSWORD
The output is an
apr1-hashed password string. Copy it for the next step.Create an environment file to store your domains, Let's Encrypt contact email, and Web UI credentials.
console$ nano .env
Add the following content, replacing the placeholder values with your own:
iniDOMAIN=DOMAIN-NAME UI_DOMAIN=UI-DOMAIN-NAME LETSENCRYPT_EMAIL=ADMIN-EMAIL UI_USERNAME=UI-USERNAME UI_PASSWORD_HASH=UI-PASSWORD-HASH
Replace:
- UI-DOMAIN-NAME: The second subdomain you pointed at your server in the Prerequisites section.
- UI-USERNAME: The username you want to use to log in to the Web UI.
- UI-PASSWORD-HASH: The
apr1hash from the previous step, with every$doubled (for example,$apr1$abc123becomes$$apr1$$abc123). Docker Compose treats a single$as a variable reference, including inside values loaded from.env.
Replace
DOMAIN-NAMEwith the same domain you used inelasticmq.conf, andADMIN-EMAILwith your email address for Let's Encrypt certificate notifications.Save and close the file.
Create the Docker Compose file.
console$ nano docker-compose.yml
Add the following configuration.
yamlservices: traefik: image: "traefik:v3.7.0" container_name: "traefik" restart: unless-stopped command: - "--log.level=INFO" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.myresolver.acme.tlschallenge=true" - "--certificatesresolvers.myresolver.acme.email=${LETSENCRYPT_EMAIL}" - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - "./letsencrypt:/letsencrypt" - "/var/run/docker.sock:/var/run/docker.sock:ro" networks: - elasticmq elasticmq: image: softwaremill/elasticmq-native:1.7.1 container_name: elasticmq volumes: - ./elasticmq.conf:/opt/elasticmq.conf - ./data:/data labels: - "traefik.enable=true" - "traefik.http.routers.elasticmq-api.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.elasticmq-api.entrypoints=websecure" - "traefik.http.routers.elasticmq-api.tls.certresolver=myresolver" - "traefik.http.services.elasticmq-api.loadbalancer.server.port=9324" restart: unless-stopped networks: - elasticmq elasticmq-ui: image: softwaremill/elasticmq-ui:1.7.1 container_name: elasticmq-ui environment: - SQS_ENDPOINT=http://elasticmq:9324 depends_on: - elasticmq labels: - "traefik.enable=true" - "traefik.http.routers.elasticmq-ui.rule=Host(`${UI_DOMAIN}`)" - "traefik.http.routers.elasticmq-ui.entrypoints=websecure" - "traefik.http.routers.elasticmq-ui.tls.certresolver=myresolver" - "traefik.http.services.elasticmq-ui.loadbalancer.server.port=3000" - "traefik.http.middlewares.elasticmq-ui-auth.basicauth.users=${UI_USERNAME}:${UI_PASSWORD_HASH}" - "traefik.http.routers.elasticmq-ui.middlewares=elasticmq-ui-auth" restart: unless-stopped networks: - elasticmq networks: elasticmq:
Save and close the file.
This manifest defines:
traefik: Terminates HTTPS for both domains and automatically obtains a Let's Encrypt certificate for each using the TLS-ALPN-01 challenge. HTTP traffic on port 80 redirects to HTTPS. The Docker socket mount lets Traefik discover theelasticmqandelasticmq-uicontainers and their routing labels.elasticmq: Runs the SQS-compatible REST API. Its Traefik labels route all requests forDOMAINto port 9324 inside the container. No host port is published, since Traefik reaches the container over the internalelasticmqDocker network.elasticmq-ui: Runs the web UI on its own subdomain,UI_DOMAIN, protected by a Traefikbasicauthmiddleware so only credentialed requests reach it. The UI application's static assets also load from absolute root paths, so it needs a dedicated domain rather than a path prefix on the same domain as the REST API.networks: The internalelasticmqnetwork lets Traefik reach both containers by name without exposing them to the host or the internet.
Start the ElasticMQ and Traefik containers.
console$ docker compose up -d
Verify the containers are running.
console$ docker compose ps
The output displays three running containers:
traefik,elasticmq, andelasticmq-ui.Check the Traefik logs to confirm it obtained a certificate for your domain.
console$ docker compose logs traefik
The output includes an
INFline readingRegister...formyresolver.acme, with noERRlines. This confirms Traefik requested a certificate from Let's Encrypt without errors.For more information on managing a Docker Compose stack, see the How To Use Docker Compose article.
Configure ElasticMQ Queues
ElasticMQ queues can be pre-configured at startup or created dynamically using the SQS API. Pre-configuration ensures queues exist immediately when the server starts.
Open the configuration file.
console$ nano ~/elasticmq/elasticmq.conf
Locate the existing
queuessection and add your queue definitions. The following example adds three queues alongside the existingdefault-queue:iniqueues { default-queue { defaultVisibilityTimeout = 30 seconds delay = 0 seconds receiveMessageWait = 0 seconds } orders-queue { defaultVisibilityTimeout = 60 seconds delay = 0 seconds receiveMessageWait = 20 seconds } notifications-queue { defaultVisibilityTimeout = 30 seconds delay = 5 seconds receiveMessageWait = 10 seconds } batch-processing-queue { defaultVisibilityTimeout = 300 seconds delay = 0 seconds receiveMessageWait = 20 seconds } }
Save and close the file.
In this configuration:
defaultVisibilityTimeout: Time in seconds a message is hidden after being received. If not deleted within this time, the message becomes visible again.delay: Time in seconds before new messages become available for consumption.receiveMessageWait: Maximum time in seconds to wait for messages when using long polling.
Restart ElasticMQ to apply the changes.
console$ docker compose restart elasticmq
Set Up Dead-Letter Queues
Dead-letter queues (DLQs) capture messages that fail processing after a specified number of attempts. This prevents problematic messages from blocking the main queue while preserving them for analysis.
Open the configuration file.
console$ nano ~/elasticmq/elasticmq.conf
Locate the queue you want to configure with a dead-letter queue (for example,
orders-queue) and add thedeadLettersQueueblock. Also add the DLQ itself as a separate queue:iniorders-queue { defaultVisibilityTimeout = 60 seconds delay = 0 seconds receiveMessageWait = 20 seconds deadLettersQueue { name = "orders-dlq" maxReceiveCount = 3 } } orders-dlq { defaultVisibilityTimeout = 60 seconds }
Save and close the file.
In this configuration:
deadLettersQueue.name: The queue that receives failed messages.deadLettersQueue.maxReceiveCount: Number of receive attempts before moving the message to the DLQ. Valid values are 1 to 1000.
Restart ElasticMQ to apply the changes.
console$ docker compose restart elasticmq
Access ElasticMQ Web UI
The ElasticMQ Web UI runs as a separate container, reachable on its own subdomain through Traefik.
Open a web browser and navigate to
https://UI-DOMAIN-NAME, replacingUI-DOMAIN-NAMEwith the UI subdomain from the Prerequisites section.Enter the
UI-USERNAMEand password you set in the Docker Compose configuration when the browser prompts for HTTP basic authentication.The dashboard displays all configured queues with their current message counts, including messages available, messages in flight, and messages delayed.
Click a queue name to view detailed statistics and send or receive test messages.
Configure Queue Persistence
By default, ElasticMQ stores all data in memory, meaning queues and messages are lost on restart. Enable persistence to retain data across restarts using an H2 database.
Open the configuration file.
console$ nano ~/elasticmq/elasticmq.conf
Add the following
messages-storagesection at the end of the file.inimessages-storage { enabled = true }
Save and close the file.
Restart ElasticMQ to apply the changes.
console$ docker compose restart elasticmq
ElasticMQ now persists all queues and messages to an H2 database file. On restart, the previous state is automatically restored.
Verify the Deployment
Validate the full request path, from the AWS CLI through the TLS front end to ElasticMQ, by testing queue operations with the AWS CLI against your domain over HTTPS.
Download the AWS CLI installer.
console$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
Extract the installer archive.
console$ unzip awscliv2.zip
Run the installer.
console$ sudo ./aws/install
Configure the AWS CLI with placeholder credentials. ElasticMQ accepts any credentials.
console$ aws configure
Enter the following when prompted:
- AWS Access Key ID:
test - AWS Secret Access Key:
test - Default region name:
us-east-1 - Default output format:
json
- AWS Access Key ID:
Confirm the domain serves a valid TLS certificate, replacing
DOMAIN-NAMEwith your domain.console$ curl -I https://DOMAIN-NAME
The output shows
HTTP/2 400. This status comes from ElasticMQ itself, which rejects a bare request with no SQS action, confirming the TLS handshake succeeded and the request reached ElasticMQ through the front end.Confirm the Web UI rejects requests without credentials, replacing
UI-DOMAIN-NAMEwith your UI subdomain.console$ curl -I https://UI-DOMAIN-NAME
The output shows
HTTP/2 401, confirming the Traefikbasicauthmiddleware is active.Confirm the Web UI accepts the credentials you configured, replacing
UI-DOMAIN-NAME,UI-USERNAME, andUI-PASSWORDwith your values.console$ curl -I -u UI-USERNAME:UI-PASSWORD https://UI-DOMAIN-NAME
The output shows
HTTP/2 200.Create a test queue, replacing
DOMAIN-NAMEwith your domain.console$ aws --endpoint-url https://DOMAIN-NAME sqs create-queue --queue-name test-queue
The output displays the queue URL.
Send a test message.
console$ aws --endpoint-url https://DOMAIN-NAME sqs send-message --queue-url https://DOMAIN-NAME/000000000000/test-queue --message-body "Hello from ElasticMQ"
The output shows the message ID and MD5 hash.
Receive the message.
console$ aws --endpoint-url https://DOMAIN-NAME sqs receive-message --queue-url https://DOMAIN-NAME/000000000000/test-queue
The output displays the message body and receipt handle.
Delete the message using the receipt handle from the previous output. Replace
RECEIPT-HANDLEwith the actual value.console$ aws --endpoint-url https://DOMAIN-NAME sqs delete-message --queue-url https://DOMAIN-NAME/000000000000/test-queue --receipt-handle RECEIPT-HANDLE
List all queues to verify the deployment.
console$ aws --endpoint-url https://DOMAIN-NAME sqs list-queues
The output displays all configured queues including the
test-queuecreated earlier.
Migrate from AWS SQS to ElasticMQ
Migrating from AWS SQS to ElasticMQ means recreating queue configurations and repointing application endpoints, since ElasticMQ implements the SQS API. Consult the current AWS SQS documentation and the ElasticMQ documentation for exact commands and options when you plan your migration.
Queue Configuration Migration
Recreate your existing AWS SQS queue definitions in ElasticMQ. List the queues in your AWS account with aws sqs list-queues, and retrieve each queue's configuration with aws sqs get-queue-attributes using the --attribute-names All option. Map the AWS attributes to their ElasticMQ configuration keys, then add the queue definitions to the ElasticMQ configuration file as described in the Configure ElasticMQ Queues section.
The AWS attributes map to ElasticMQ configuration as follows:
VisibilityTimeoutmaps todefaultVisibilityTimeoutDelaySecondsmaps todelayReceiveMessageWaitTimeSecondsmaps toreceiveMessageWaitRedrivePolicymaps todeadLettersQueue
FIFO Queue Migration
ElasticMQ supports FIFO queues with message deduplication. Define a FIFO queue by adding the .fifo suffix to the queue name and setting fifo = true in the queue's configuration block, along with contentBasedDeduplication = true to deduplicate messages by content hash. FIFO queues support message group IDs for ordered processing within groups, and deduplication using either content-based hashing or explicit deduplication IDs.
Dead-Letter Queue Setup
Configure dead-letter queues in ElasticMQ to match your AWS SQS redrive policies. For each queue with a redrive policy in AWS, create a corresponding DLQ in ElasticMQ and set the maxReceiveCount to match your AWS maxReceiveCount value.
Message Migration
ElasticMQ is best suited for development, testing, continuous integration (CI), and controlled self-hosted environments. For production systems, validate durability, authentication, monitoring, and availability requirements before replacing AWS SQS.
For queues with existing messages that need preservation:
Drain existing messages: Process all messages in AWS SQS before switching to ElasticMQ.
Dual-write period: Configure producers to send messages to both AWS SQS and ElasticMQ during the migration window.
Cutover: Once consumers are migrated and the AWS queues are empty, disable the AWS SQS producers.
Application Code Updates
Update application code to target the ElasticMQ endpoint instead of AWS SQS. Because ElasticMQ implements the SQS query API, AWS SDK clients typically need only an endpoint change. Point the SDK client at the ElasticMQ endpoint (for example, set the boto3 client's endpoint_url to http://SERVER-IP:9324), specify the region, and provide placeholder credentials, since ElasticMQ accepts any values. For applications or frameworks that read endpoint configuration from environment variables, set AWS_ENDPOINT_URL, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY accordingly.
Migration Considerations
- Message Ordering: AWS SQS standard queues provide best-effort ordering. ElasticMQ standard queues behave similarly. Use FIFO queues if strict ordering is required.
- Message Size: AWS SQS supports messages up to 256 KB. ElasticMQ has the same limit by default when using
sqs-limits = strict. - Visibility Timeout: Ensure your ElasticMQ visibility timeouts match your AWS configuration to prevent duplicate processing.
- Long Polling: ElasticMQ supports long polling with
receiveMessageWait. Configure this to reduce empty responses and API calls. - Authentication: ElasticMQ accepts any access key ID and secret access key by default and never validates the AWS Signature Version 4 signature, so a reverse proxy alone does not restrict who can call the API. Restrict network access instead: allow only the required client IP addresses with
ufw allow from CLIENT-IP to any port 443 proto tcp, or create an equivalent Vultr Cloud Firewall group and attach it to the server. - Monitoring: Replace CloudWatch metrics with ElasticMQ's web UI or export metrics to Prometheus using a custom exporter.
- High Availability: AWS SQS provides built-in redundancy. ElasticMQ does not provide AWS SQS-style managed high availability. For critical production workloads, evaluate whether AWS SQS or another production-grade queue is more appropriate.
Conclusion
You have deployed ElasticMQ as a self-hosted alternative to AWS SQS, fronted by Traefik with automatic HTTPS. This article covered Docker-based installation, TLS termination for a public domain, queue configuration with visibility timeouts and delays, dead-letter queue setup for failed message handling, the web UI for monitoring, message persistence using H2 database, and migration strategies from AWS SQS. ElasticMQ provides a lightweight, SQS-compatible message queue suitable for development, testing, and production workloads. For additional configuration options and advanced features, visit the official ElasticMQ documentation.