PHP
Celery
RabbitMQ
Task Queue
Web Development

How to post a task on a celery-rabbitmq queue in PHP?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

PHP can publish messages to RabbitMQ easily, but publishing a task that a Celery worker will actually accept is more specific than just dropping JSON into a queue. Celery has its own task message protocol, so the PHP producer must match the queue, serializer, headers, and body structure expected by the Python worker.

Understand The Real Constraint

RabbitMQ is only the broker. Celery is the task system that defines how messages are encoded and interpreted. That means this PHP code is not enough:

php
$channel->basic_publish($msg, '', 'celery');

Publishing to the queue name alone does not make the message a valid Celery task. The worker still expects a Celery-compatible payload.

For current Celery protocol v2, metadata such as the task name and task id lives in headers, while the serialized arguments live in the body. That is the part most ad hoc examples leave out.

Publish A Celery-Compatible Task From PHP

Using php-amqplib, you can publish a minimal task message like this:

php
1<?php
2
3require __DIR__ . '/vendor/autoload.php';
4
5use PhpAmqpLib\Connection\AMQPStreamConnection;
6use PhpAmqpLib\Message\AMQPMessage;
7
8$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'guest', 'guest');
9$channel = $connection->channel();
10
11$taskId = bin2hex(random_bytes(16));
12$args = [10, 20];
13$kwargs = new stdClass();
14$embed = null;
15
16$body = json_encode([$args, $kwargs, $embed], JSON_UNESCAPED_SLASHES);
17
18$headers = [
19    'lang' => 'py',
20    'task' => 'tasks.add',
21    'id' => $taskId,
22    'argsrepr' => '[10, 20]',
23    'kwargsrepr' => '{}',
24    'origin' => 'php-client',
25];
26
27$msg = new AMQPMessage(
28    $body,
29    [
30        'content_type' => 'application/json',
31        'content_encoding' => 'utf-8',
32        'correlation_id' => $taskId,
33        'delivery_mode' => 2,
34        'application_headers' => new \PhpAmqpLib\Wire\AMQPTable($headers),
35    ]
36);
37
38$channel->basic_publish($msg, '', 'celery');
39
40$channel->close();
41$connection->close();

This example assumes:

  • the Celery worker is consuming from the celery queue
  • the worker accepts JSON serialization
  • the task name is tasks.add

If the worker uses a different queue or serializer, the PHP side must match it exactly.

Match The Python Worker Configuration

A compatible Celery worker might look like this:

python
1from celery import Celery
2
3app = Celery(
4    "demo",
5    broker="amqp://guest:[email protected]:5672//",
6)
7
8app.conf.task_serializer = "json"
9app.conf.accept_content = ["json"]
10
11@app.task(name="tasks.add")
12def add(a, b):
13    return a + b

If the PHP producer sends JSON but the worker only accepts pickle or msgpack, the task will not be processed correctly.

That is why Celery interoperability is not only about RabbitMQ connectivity. The application-level protocol has to line up too.

When A Bridge Service Is Simpler

For one-off integrations, directly publishing a Celery message from PHP can work. But for long-term maintenance, a small Python bridge API is often cleaner.

Instead of reimplementing Celery protocol details in PHP, the PHP app can call an internal HTTP endpoint, and that Python service can use app.send_task(...) or apply_async(...) natively.

That approach reduces drift when Celery configuration changes, especially around:

  • serialization
  • routing
  • retries
  • result backends

It also avoids duplicating Celery-specific message assembly logic in another language.

Common Pitfalls

One common mistake is publishing a plain JSON object with task, args, and kwargs in the body and assuming the Celery worker will always accept it. Current Celery protocol uses headers as part of the task envelope.

Another issue is sending the message to the celery queue when the worker actually listens on a custom queue.

A third problem is mismatched serializers. If the worker is configured to accept JSON only, a differently encoded PHP message will be rejected.

Finally, many examples ignore retries, result handling, or exchange routing and only work in a toy local setup. Production interoperability needs those details to be deliberate.

Summary

  • RabbitMQ is only the broker; Celery defines the task message format.
  • PHP can publish Celery tasks, but the queue, serializer, headers, and body must match the worker configuration.
  • For Celery protocol v2, important metadata such as task name and id lives in headers.
  • A php-amqplib publisher can work for controlled cases where the protocol is stable and known.
  • For larger systems, a small Python bridge service is often easier to maintain than handcrafting Celery messages in PHP.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.