How to Deploy ElasticMQ as an AWS SQS Alternative

Updated on 29 July, 2026
Deploy ElasticMQ as a self-hosted AWS SQS alternative using Docker, Traefik, HTTPS, persistent queues, dead-letter queues, and a web UI.
How to Deploy ElasticMQ as an AWS SQS Alternative header image

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-ui container) 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.

  1. 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.
  2. Switch to the project directory.

    console
    $ cd ~/elasticmq
    
  3. Create the ElasticMQ configuration file.

    console
    $ nano elasticmq.conf
    
  4. Add the following configuration to define the server settings and pre-create queues, replacing DOMAIN-NAME with the domain you pointed at your server in the Prerequisites section.

    ini
    include 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-hostname stays at 0.0.0.0 so 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.
  5. Generate a password hash to protect the Web UI with HTTP basic authentication, replacing UI-PASSWORD with 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.

  6. 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:

    ini
    DOMAIN=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 apr1 hash from the previous step, with every $ doubled (for example, $apr1$abc123 becomes $$apr1$$abc123). Docker Compose treats a single $ as a variable reference, including inside values loaded from .env.

    Replace DOMAIN-NAME with the same domain you used in elasticmq.conf, and ADMIN-EMAIL with your email address for Let's Encrypt certificate notifications.

    Save and close the file.

  7. Create the Docker Compose file.

    console
    $ nano docker-compose.yml
    
  8. Add the following configuration.

    yaml
    services:
      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 the elasticmq and elasticmq-ui containers and their routing labels.
    • elasticmq: Runs the SQS-compatible REST API. Its Traefik labels route all requests for DOMAIN to port 9324 inside the container. No host port is published, since Traefik reaches the container over the internal elasticmq Docker network.
    • elasticmq-ui: Runs the web UI on its own subdomain, UI_DOMAIN, protected by a Traefik basicauth middleware 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 internal elasticmq network lets Traefik reach both containers by name without exposing them to the host or the internet.
  9. Start the ElasticMQ and Traefik containers.

    console
    $ docker compose up -d
    
  10. Verify the containers are running.

    console
    $ docker compose ps
    

    The output displays three running containers: traefik, elasticmq, and elasticmq-ui.

  11. Check the Traefik logs to confirm it obtained a certificate for your domain.

    console
    $ docker compose logs traefik
    

    The output includes an INF line reading Register... for myresolver.acme, with no ERR lines. 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.

  1. Open the configuration file.

    console
    $ nano ~/elasticmq/elasticmq.conf
    
  2. Locate the existing queues section and add your queue definitions. The following example adds three queues alongside the existing default-queue:

    ini
    queues {
      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.
  3. 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.

  1. Open the configuration file.

    console
    $ nano ~/elasticmq/elasticmq.conf
    
  2. Locate the queue you want to configure with a dead-letter queue (for example, orders-queue) and add the deadLettersQueue block. Also add the DLQ itself as a separate queue:

    ini
    orders-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.
  3. 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.

  1. Open a web browser and navigate to https://UI-DOMAIN-NAME, replacing UI-DOMAIN-NAME with the UI subdomain from the Prerequisites section.

  2. Enter the UI-USERNAME and password you set in the Docker Compose configuration when the browser prompts for HTTP basic authentication.

  3. The dashboard displays all configured queues with their current message counts, including messages available, messages in flight, and messages delayed.

  4. 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.

  1. Open the configuration file.

    console
    $ nano ~/elasticmq/elasticmq.conf
    
  2. Add the following messages-storage section at the end of the file.

    ini
    messages-storage {
      enabled = true
    }
    

    Save and close the file.

  3. 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.

  1. Download the AWS CLI installer.

    console
    $ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    
  2. Extract the installer archive.

    console
    $ unzip awscliv2.zip
    
  3. Run the installer.

    console
    $ sudo ./aws/install
    
  4. 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
  5. Confirm the domain serves a valid TLS certificate, replacing DOMAIN-NAME with 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.

  6. Confirm the Web UI rejects requests without credentials, replacing UI-DOMAIN-NAME with your UI subdomain.

    console
    $ curl -I https://UI-DOMAIN-NAME
    

    The output shows HTTP/2 401, confirming the Traefik basicauth middleware is active.

  7. Confirm the Web UI accepts the credentials you configured, replacing UI-DOMAIN-NAME, UI-USERNAME, and UI-PASSWORD with your values.

    console
    $ curl -I -u UI-USERNAME:UI-PASSWORD https://UI-DOMAIN-NAME
    

    The output shows HTTP/2 200.

  8. Create a test queue, replacing DOMAIN-NAME with your domain.

    console
    $ aws --endpoint-url https://DOMAIN-NAME sqs create-queue --queue-name test-queue
    

    The output displays the queue URL.

  9. 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.

  10. 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.

  11. Delete the message using the receipt handle from the previous output. Replace RECEIPT-HANDLE with 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
    
  12. 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-queue created 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:

  • VisibilityTimeout maps to defaultVisibilityTimeout
  • DelaySeconds maps to delay
  • ReceiveMessageWaitTimeSeconds maps to receiveMessageWait
  • RedrivePolicy maps to deadLettersQueue

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:

  1. Drain existing messages: Process all messages in AWS SQS before switching to ElasticMQ.

  2. Dual-write period: Configure producers to send messages to both AWS SQS and ElasticMQ during the migration window.

  3. 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.

Comments