# openwisp-controller: a Django control plane for OpenWrt fleets

> openwisp-controller is the Django application that sits between an operator and a fleet of OpenWrt devices, handling adoption, provisioning, configuration management, VPN configuration and x509 certificate lifecycle. It is deliberately built as one module of a wider OpenWISP ecosystem rather than a standalone appliance, and the licence is a custom one that deserves a read.

**openwisp/openwisp-controller** — Network and WiFi controller: provisioning, configuration management and updates, (pull via openwisp-config or push via SSH), x509 PKI management and more. Mainly OpenWRT, but designed to work also on other systems.

- Repository: https://github.com/openwisp/openwisp-controller
- Website: https://openwisp.io/docs/stable/controller/
- Stars: 785 · Forks: 333
- Language: Python
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/openwisp-openwisp-controller

## One module of an ecosystem, not a standalone appliance

OpenWISP Controller is a configuration manager that allows you to automate several networking tasks like adoption, provisioning, management of VPN configuration, X509 certificates automatic generation, revocation of x509 certificates and a lot more features.

The most important sentence in the README is the one that follows. OpenWISP is not only an application designed for end users, but can also be used as a framework on which custom network automation solutions can be built on top of its building blocks.

That framing tells you how to evaluate it. openwisp-controller is one of several packages, and the list of sibling building blocks in the README is a map of what the project deliberately does not do itself:

openwisp-monitoring provides device status monitoring, collection of metrics, charts, alerts and the possibility to define custom checks. openwisp-firmware-upgrader handles automated firmware upgrades, for single devices or mass network upgrades. openwisp-radius, based on FreeRADIUS, allows you to implement network access authentication systems like 802.1x WPA2 Enterprise, captive portal authentication and Hotspot 2.0. openwisp-network-topology collects and visualizes network topology data from dynamic mesh routing daemons or other network software such as OpenVPN, and can be used together with openwisp-monitoring. openwisp-ipam manages the assignment of IP addresses used in the network. openwisp-notifications lets users be aware of important events happening in the network.

So the choice is: assemble the modules you need, or use the controller alone for configuration and certificates and handle monitoring, radius and firmware yourself.

The primary language is Python, the project is a Django application, and the homepage is the OpenWISP documentation for the controller. There is also a public demo at openwisp.org/demo.html, which the README recommends as a quick overview before you install anything.

## Adoption, provisioning, and two ways to reach the device

The repository description names the two mechanisms that decide how much work an adoption involves: provisioning, configuration management and updates, pulled via openwisp-config or pushed via SSH.

That distinction is the operational heart of the project. In a pull model, the device connects out to the controller and fetches its configuration, which suits devices behind NAT or on intermittent links, and it is the same shape as the way most remote device fleets work. In a push model, the controller reaches the device over SSH and applies configuration directly, which suits a LAN where the controller has stable addresses for everything and where you want the change to happen now rather than at the next device poll.

Choosing between them is a fleet decision rather than a per device decision, and it has consequences for your network design. The push path implies the controller can route to every device and that you are running SSH with keys to each of them, and the pull path implies each device needs a reachable controller URL and enough configuration to retry.

The requirements list gives a good sense of how the push path is implemented, because SSH tooling is a first class dependency. paramiko at 5.0.0 and scp at 0.16.1 are the SSH and file copy libraries, and django-x509 is the package that supplies the x509 certificate machinery, taken from a branch archive rather than PyPI. The x509 handling is not a reimplementation inside this project, which is the pattern across the whole dependency list: netjsonconfig for NetJSON configuration, django-loci for the location data used in network topology, django-flat-json-widget for JSON editing in the admin, and openwisp-users for the user model.

The certificate work is worth calling out, because it is the part that a plain configuration manager cannot do. Automatic certificate generation and revocation means the controller acts as a small internal CA for the devices it manages, and django-x509 is the library that carries that. It also means the private key handling, the renewal and the revocation of device identities are all inside this project's blast radius, which is a good reason to look at the django-x509 dependency carefully before deploying it with production devices.

## Running it locally, and the Redis dependency

The repository ships a docker-compose.yml for local development, and it opens with a note that this Docker image is for development purposes only. That comment matters, because the file is not a production deployment recipe.

The stack is small. There is a controller service, a redis service, and nothing else:

```yaml
services:
  controller:
    image: openwisp/controller-development:latest
    environment:
      - REDIS_URL=redis://redis:6379
    build:
      context: .
    ports:
      - 8000:8000
    depends_on:
      - redis

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    entrypoint: redis-server --appendonly yes
```

The Redis service is started with the appendonly flag so that its state survives a restart, and the controller receives the REDIS_URL pointing at it. The controller is exposed on port 8000.

That Redis dependency is the interesting part of the local setup, because it is not optional decoration. The requirements file installs openwisp-utils with the celery and channels extras, which means the controller expects a task queue and a websocket layer, and the Docker Compose file supplies exactly one backing service for them. A deployment that omits Redis will not start cleanly.

The Docker image for the controller itself is openwisp/controller-development:latest, and the compose file also has a build context pointing at the repository root, so the image can be rebuilt from source rather than pulled.

For the package itself, the setup.py names it openwisp-controller and the README badges point at the PyPI page, so the distribution name is openwisp-controller. The repository does not document an install command in the README itself, so the install path is the package name plus the documentation, and the setup.py pulls the runtime requirements out of requirements.txt at build time.

## A Django app with a fairly wide dependency surface

The requirements file and the setup.py tell you what kind of project this is.

The Django side is well covered: django-sortedm2m for sorted many to many relations in the admin, django-reversion for versioning the configuration objects, django-taggit for tags, django-import-export for data import and export, django-cache-memoize for caching, and djangorestframework-gis for the geospatial API. There is also geoip2 for IP geolocation, shortuuid for identifiers and netaddr for address handling.

The OpenWISP family is pulled from GitHub branch archives rather than from PyPI, each pinned to a specific branch. netjsonconfig, django-x509, django-loci, django-flat-json-widget, openwisp-users, openwisp-utils with the celery and channels extras, openwisp-notifications and openwisp-ipam. Every one of these is a direct URL to a tarball, which is the single most operationally important fact in the file.

The reason is the development model. The OpenWISP modules move in step with each other, and the controller pins a specific branch of each sibling so that the set is known to be compatible. The cost is that your dependency resolution is no longer independent, and an upgrade of one OpenWISP module may require a matching upgrade of the others and of the controller itself. pin these deliberately and upgrade them together.

The pyproject.toml shows the tooling behind the code. Coverage is configured with parallel runs and multiprocessing and thread concurrency, and migrations and tests are omitted from the source list. isort is configured with django and django_x509 as known third party, openwisp_users and openwisp_utils as known first party, an 88 character line length, and the multi_line_output 3 style with trailing commas. The project uses black for formatting, as the README badge states.

The setup.py itself is small and does one unusual thing. Its get_install_requires function parses requirements.txt directly, skipping comments, empty lines, and entries starting with http or git, and passing the rest to install_requires. That is why the requirements file is the single source of truth for runtime dependencies, and it is a pattern worth copying.

## Where the controller stops and the ecosystem starts

The honest limits come from the modular design rather than from anything missing.

The first is that a controller alone is not a network management system. Without openwisp-monitoring there is no device status, no metrics, no charts and no alerting, and without openwisp-firmware-upgrader there are no firmware rollouts. If what you are looking for is a dashboard that tells you whether the network is healthy, this is the wrong package, and openwisp-monitoring is the one to look at.

The second is that the device model is OpenWrt shaped. The description says the project is mainly OpenWRT, but designed to work also on other systems, and the requirements make the mechanism visible: openwisp-config on the device side is the counterpart that pulls configuration, and the x509 and SSH machinery assumes a Linux based device you can log into. A fleet of switches from a vendor with a closed management interface is not a good fit.

The third is the deployment story. There is a Dockerfile and a development compose file, but the README does not document a production deployment path, so you are expected to build one, and a Django application with Celery, a websocket layer, Redis, a relational database and a small internal CA is not a small deployment.

The alternatives differ in what they assume. Writing the configuration templates yourself and pushing them with your own tooling, Ansible for example, gives you full control and no new platform, and you lose the certificate lifecycle and the device inventory that the controller maintains. Running a configuration management tool designed for servers rather than routers gives you a mature framework and none of the network specifics, such as NetJSON templates and per device certificate issuance.

A further detail worth checking before you commit: the licence is recorded as a custom one that GitHub cannot categorize, and the setup.py declares it as GPL3 with the classifier for the GNU General Public License v3. The README points at the LICENSE file rather than stating the terms, so that file is the one to read if your organisation's legal process needs the actual text.

On maintenance, the repository is not archived and the last push was on 2026-10-20, with releases 1.3.1 on 2026-10-15, 1.3.0 on 2026-09-03 and 1.2.3 on 2026-04-09. That is a steady release cadence for infrastructure software, and the CHANGES.rst file is where the per release detail lives.

## Conclusion

Adopt openwisp-controller if you run a fleet of OpenWrt devices and want a single place to provision them, push configuration and manage their certificates, and you are prepared to run a Django application with Redis and a relational database. Do not adopt it for a handful of home routers, where the device web interface is enough, and do not assume it is a general network management system. Verify first that the licence file matches what your organisation expects, that your devices can be reached by the pull or push method you intend to use, and that the related OpenWISP modules you actually need are separate packages you will have to deploy as well.

## FAQ

### What does openwisp-controller do?

It is a configuration manager that automates networking tasks on OpenWrt devices: adoption, provisioning, management of VPN configuration, automatic generation of X509 certificates, revocation of those certificates, and updates, either pulled by the device via openwisp-config or pushed by the controller over SSH.

### Is openwisp-controller a complete network management system?

No. It is one module of the OpenWISP ecosystem. Device status monitoring and metrics live in openwisp-monitoring, firmware rollouts in openwisp-firmware-upgrader, RADIUS in openwisp-radius, topology in openwisp-network-topology, IP address management in openwisp-ipam and event notifications in openwisp-notifications.

### What does openwisp-controller need to run locally?

The development docker-compose.yml starts two services, a controller on port 8000 and a redis service, with the controller configured through REDIS_URL. The compose file notes that the image is for development purposes only, and the runtime requirements install openwisp-utils with the celery and channels extras.

### How are openwisp-controller's Python dependencies managed?

The setup.py parses requirements.txt at build time and passes the non comment entries to install_requires, so that file is the single source of truth. Several OpenWISP packages are pinned to GitHub branch archives rather than PyPI, so the modules have to be upgraded together.

### What license is openwisp-controller released under?

The setup.py declares the license as GPL3 with the GNU General Public License v3 classifier, and the README points to the LICENSE file in the repository root for the terms. The project metadata records it as a custom licence that GitHub cannot categorize.

## Sources

- [Issues](https://github.com/openwisp/openwisp-controller/issues)
- [openwisp/openwisp-controller on GitHub](https://github.com/openwisp/openwisp-controller)
- [Project website](https://openwisp.io/docs/stable/controller/)
- [README](https://github.com/openwisp/openwisp-controller/blob/master/README.md)
- [Releases](https://github.com/openwisp/openwisp-controller/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/openwisp-openwisp-controller
