Setting up NTFY with Ngnix-Proxy-Manager, authentication and Ansible notifications (Update)

(Update) I added the use of access tokens.

Hi there,

today I have something pretty cool.

NTFY. An HTTP-based notification server, that also has a nice phone app. It allows me to get notification from basically anything I want. To tell you the truth, I don’t have to may use cases so far, except for failed backups. But, you can send notifications with Ansible (though it took me a while to get it running with authentication, even though it’s very simple), which makes the possibilities basically endless.

I will also show the deployment and configuration of Nginx-Proxy-Manager so that we can easily secure our NTFY server with a certificate.

Let’s begin.

I will be using docker for the deployment, since it makes this a breeze and the base OS doesn’t really matter. For the OS, I will be using Rocky Linux 8.9.

Deploying the docker containers

Docker installation

We will start with the installation of docker.

First, start with adding the docker repo.

# Adding the repository
docker :: ~ » sudo dnf config-manager --add-repo

Next, install the required packages.

# Installing the docker packages
docker :: ~ » sudo dnf -y install docker-ce docker-ce-cli docker-compose-plugin

Last step for this. Enabling and starting the service.

# Enable the service and start it
docker :: ~ » sudo systemctl --now enable docker

Alright. Let’s continue with the ntfy server deployment.

Deploying NTFY

Create a subfolder for the docker-compose file. I will use the home folder for this. In here, we will create a “docker-compose.yml” file with the following content. The content is from the main documentation with just a couple of changes.

Take note of the changed port marked in red. You can choose here whatever port you want, we will need this later for the nginx-proxy-server. If you don’t want to use nginx for the reverse proxy, just leave it at 80. I also highlighted the paths to the NTFY configuration, cache and lib files in green. We also need to create those.

# Create the folder
docker :: ~ » mkdir ntfy

# Navigate into that folder
docker :: ~ » cd ntfy

# Create a file named docker-compose.yml
docker :: ntfy » vim docker-compose.yml
version: "2.3"
    image: binwiederhier/ntfy
    container_name: ntfy
      - serve
      - TZ=Europe/Berlin
      - /var/cache/ntfy:/var/cache/ntfy
      - /etc/ntfy:/etc/ntfy
      - /var/lib/ntfy:/var/lib/ntfy
        test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
        interval: 60s
        timeout: 10s
        retries: 3
        start_period: 40s
    restart: unless-stopped

Create the folders.

Create the folders. "-p" will create the parent folder if it doesn't exist
docker  :: ntfy » sudo mkdir /var/cache/ntfy -p
docker  :: ntfy » sudo mkdir /etc/ntfy
docker  :: ntfy » sudo mkdir /var/lib/ntfy -p

We also need a “server.yml” in the “/etc/ntfy” folder with a few settings we should set. I highlighted the parts that you should adapt to your environment. The paths are the same as in the docker-compose file above, marked in green.

If you don’t need authentication, remove the lines that start with “auth”.

docker :: ntfy » sudo vim /etc/ntfy/server.yml
base-url: "<your-fqdn-url>"
upstream-base-url: ""
listen-http: ":80"
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
behind-proxy: true
# This creates a secondary database used to manage your users and permissions
auth-file: /var/lib/ntfy/auth.db
auth-default-access: "deny-all"
   pragma journal_mode = WAL;
   pragma synchronous = normal;
   pragma temp_store = memory;
   pragma busy_timeout = 15000;
# Rate limiting in case a message source goes a bit nuts.
visitor-subscription-limit: 10
visitor-attachment-total-size-limit: "50M"
visitor-attachment-daily-bandwidth-limit: "100M"

Ok. Now we can deploy the container. You have to be in the folder with the “docker-compose.yml” file.

# Deploy the docker container
docker :: ntfy » sudo docker compose up -d

At this point, the container should be up and running. Check the status of the container with the “docker ps” command.

# Check container status
docker :: ntfy » sudo docker ps
CONTAINER ID   IMAGE                                        COMMAND                  CREATED         STATUS                   PORTS                                                                              NAMES
b1d4e03a2278   binwiederhier/ntfy                           "ntfy serve"             4 minutes ago   Up 4 minutes (healthy)>80/tcp                                                               ntfy

Alright. One more thing before we can check the WebUI. We have to set up the firewall to allow the port 8880/TCP.

# Add the firewall rule
docker :: ntfy » sudo firewall-cmd --add-port=8880/tcp
docker :: ntfy » sudo firewall-cmd --add-port=8880/tcp --permanent

Ok. Now we should be able to access the UI. Open a browser and type in the IP of the server with the port at the end. For example and you should see something like this.

At this point normally we could publish notifications, add new topics and so on. But we are not done yet (and we set the default to deny-all). Primarily because I want to have authentication and SSL. To actually receive notifications with your phone, you need an SSL certificate anyway. So let’s take care of that first.

For this, I will be deploying the Nginx-Proxy-Manager. You could also just use a nginx server to provide the reverse proxy, but I am lazy and also want to showcase the proxy-manager. Let’s continue.

Deploying Nginx-Proxy-Manager

We will start the same way. Create a subfolder and a “docker-compose.yml” file with the following content.

# Navigate back to the home folder
docker :: ntfy » cd ~

# Create a subfolder for the nginx-proxy-manager
docker :: ~ » mkdir nginx-proxy-manager

# Navigate into that folder
docker :: ~ » cd nginx-proxy-manager

# Create the file
docker :: nginx-proxy-manager » vim docker-compose.yml
version: '3.8'
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
      # These ports are in format <host-port>:<container-port>
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP

    # Uncomment the next line if you uncomment anything in the section
    # environment:
      # Uncomment this if you want to change the location of
      # the SQLite DB file within the container
      # DB_SQLITE_FILE: "/data/database.sqlite"

      # Uncomment this if IPv6 is not enabled on your host
      DISABLE_IPV6: 'true'

      - /etc/nginx-proxy-manager/data:/data
      - /etc/nginx-proxy-manager/letsencrypt:/etc/letsencrypt

I highlighted the paths in red which you might want to change.

Create the highlighted folders.

# Create the folders. "-p" will create the parent folder if it doesn't exist
docker :: nginx-proxy-manager » sudo mkdir /etc/nginx-proxy-manager/data -p
docker :: nginx-proxy-manager » sudo mkdir /etc/nginx-proxy-manager/letsencrypt -p

Ok. Now let’s configure the firewall.

# Add the firewall rules
docker :: nginx-proxy-manager » sudo firewall-cmd --add-service http --add-service https --permanent
docker :: nginx-proxy-manager » sudo firewall-cmd --add-service http --add-service https
docker :: nginx-proxy-manager » sudo firewall-cmd --add-service 81/tcp --permanent
docker :: nginx-proxy-manager » sudo firewall-cmd --add-service 81/tcp

Now, let’s start the container.

# Starting the docker container
docker :: nginx-proxy-manager » sudo docker compose up -d

Alright. That’s it for the deployment. Next, we will configure the access control on the NTFY server, to make sure only your systems can create notifications. For this, we will have to connect to the NTFY container.

Server Configuration

NTFY Access Control (Update)

Username Password

Let’s connect to the container. First, get the container ID.

# List container name and ID
docker :: ~ » sudo docker ps
CONTAINER ID   IMAGE                                        COMMAND                  CREATED         STATUS                   PORTS                                                                              NAMES
b1d4e03a2278   binwiederhier/ntfy                           "ntfy serve"             10 minutes ago   Up 10 minutes (healthy)>80/tcp                                                               ntfy

Now that we have the ID we can connect to it. We don’t have to input the whole ID, as long as it’s unique. In my case, the first 2 characters are enough.

After that, we can create an “admin” user.

# Connect to the ntfy container
docker :: ~ » sudo docker exec -it b1 /bin/sh

# Create a admin user
/ # ntfy user add --role=admin admin
user admin added with role admin

Next, I want an “ansible” user. This is for my ansible roles, I will show this later.

# Create a normal user
/ # ntfy user add ansible
user ansible added with role user

Now we create a topic “Ansible-Notifications” in my case and the read-write permissions for the “ansible” user.

# Create a topic and grant permissions
/ # ntfy access ansible Ansible-Notifications rw

Access Token

If you want to use access tokens, use the following command. If you don’t want it to expire, just remove the “–expires=120d” option.

# Create an access token for the "ansible" user
/ # ntfy token add -–expires=120d –-label=”TOKEN” ansible
token tk_fqqq93msikzgdzae8sso5s32fnodg created for user ansible, expires Sun Feb  25 08:58:49 UTC 2030

That’s it for the permissions, for now. You can of course create more. I also created a backup user and topic.

Nginx-Proxy-Manager configuration

Now we will set up a Let’s Encrypt certificate using the Nginx-Proxy-Manager. For this, I will assume that you have the correct ports forwarded to your Nginx-Proxy-Server and have a domain registered.

Open a browser and type in the URL for your nginx-proxy-server. For example

The default login credentials are.

password: changeme

You will immediately be asked to change the credentials. After this, you should be on the dashboard.

You might notice, that I already have 2 reverse proxy configurations. Your setup should say “0 Proxy Hosts” Just ignore that in my screenshot.

First, we need a certificate. Navigate to “SSL Certificates” and click on “Add SSL Certificate”.

Set it up how you need it with your domain and click on “Save”. This should generate a new Let’s Encrypt certificate for you.

Once that’s done, navigate to “Hosts” -> “Proxy Hosts” and click on “Add proxy Host”

Define the hostname, make sure your DNS is configured correctly, type in the forward IP and the port we defined earlier for the ntfy server.

Under the SSL index, select your newly generated certificate and click on save.

That’s it. It should now look similar to this.

Now we should be able to browse via https to the ntfy page.

Open your browser and type in the URL you defined. Something like If you can reach it we can test the notification function. If not, check your DNS configuration and firewall setup.

Testing the Notification

Let’s test it. For this we will use curl. With “-u” you can set the “username”:”password” and with “-d” you define the message. The last part of the URL is the topic you want to send it to.

# Send a test notification to the Ansible Topic.
fedora :: ~ » curl -u ansible:tk_gys85asdfa323cq -d TEST https://<fqdn>/Ansible

If you want to use the token, use the following command. It’s the same, just remove the username and replace the password with the access token.

fedora :: ~ » curl -u :tk_fqqq93msikzgdzae8sso5s32fnodg -d TEST https://<fqdn>/Ansible

Ok. Open your browser and navigate to the NTFY page. Since it’s our first login, we have to subscribe to a topic.

Type in the topic and your credentials.

And this is, what it should look like.

Alright. Let’s take a look at a Ansible task.

Ansible Task Notification (Update)

The next few file locations really depend on your setup of Ansible, but I will try to make it as clear as possible.

I have a role for my linux servers. This does things like to install updates, create folders and local users etc.

The structure looks like this. Folders are blue and files orange.

  • ansible-role-all.yml
  • ansible-role-all
    • files
    • tasks
      • main.yml
      • ntfy_notification.yml
    • vars
      • main.yml

There are a few more tasks, but those are not really relevant for this, so lets try to keep this simple.

Here is the content of the “vars” “main.yml” file.

# ntfy credentials:
ntfy_user: ansible
ntfy_password: <super secure password>
ntfy_topic: Ansible
ntfy_access_token: <your-access-token>

And here is the content of the “tasks” “main.yml” and “ntfy_notification.yml” file.


- import_tasks: ntfy_notificaton.yml
  tags: ntfy


- name: Send update
    url: "{{ ntfy_topic }}"
    method: POST
     Title: "NTFY Title"
    body: "This is the text you want to send. You can also use ansible variables here like {{ inventory_hostname }}"
    password: "{{ ntfy_password }}"
    user: "{{ ntfy_user }}"
    force_basic_auth: true

Here is a version using an access token.

- name: Send update
    url: "{{ ntfy_topic }}"
    method: POST
       Title: "NTFY Title"
       Authorization: "Bearer {{ ntfy_access_token }}"
    body: "This is the text you want to send. You can also use ansible variables here like {{ inventory_hostname }}"
    force_basic_auth: true

So, what does it look like. Let’s run the ansible role.

# Running the ansible role
fedora :: Ansible » ansible-playbook -i ansible-role-all.yml --tags ntfy
PLAY [all:!localhost] ******************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************
ok: [nextcloud]

TASK [ansible-role-all : Send update] ******************************************************************************************************
ok: [nextcloud]

PLAY RECAP ******************************************************************************************************************  
nextcloud                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

On the WebUI.

By the way. You should encrypt the ansible vars file, since it contains cleartext password. Here a quick guide on encrypting files in ansible.

Ansible File Encryption (Optional)

Use the ansible-vault command to encrypt the files.

fedora :: Ansible » ansible-vault encrypt ansible-role-all/vars/main.yml
New Vault password: 
Confirm New Vault password: 
Encryption successful

If you want to edit the file, use the “edit” option. This will open the file in nano.

fedora :: Ansible » ansible-vault edit ansible-role-all/vars/main.yml

To unencrypt the file, use the “decrypt” option.

fedora :: Ansible » ansible-vault decrypt ansible-role-all/vars/main.yml

Ok. That’s it. I will try to make more use of this whole ansible in combination with ntfy stuff. A use case, for me, would be for longer tasks to inform me when they are done. But I have to experiment with it. Anyways. Hope you found this interesting.

If you want to know how to update your Docker containers with Ansible. Here is an older post, with the tasks for that.

Till next time.

This Post Has 7 Comments

  1. NeoPlayer

    Hi! Do you know the parameter for using ntfy notifications with access tokens instead of username and password?

    1. Gökhan


      you can create a token with:
      ntfy token add –expires=120d –label=”TOKEN” username
      This needs an existing user. Once that’s done, you can list the tokens with this command:
      ntfy token list

      To send a message with the token rather than the username and password use this command (note the “:” after the -u) :
      curl -u :token-id -d “This is a token test message” “https://ntfy-url/Topic”

      It’s basically the same command, just remove the usename and replace the password with the token.

      Hope this helps.

      1. NeoPlayer

        Thanks for the answer, but I’m refering to using the acces token with the ansible module uri.
        For example with the ntfy_notification.yml, that you explained with this post.

  2. NeoPlayer

    I tried with the headers parameter of the uri module using the base64 as the ntfy documentation point but, I keep getting this error: FAILED! => {“changed”: false, “msg”: “argument ‘headers’ is of type and we were unable to convert to dict: dictionary update sequence element #0 has length 1; 2 is required”}

    1. Gökhan

      Ah I misread your first post.
      This is how it worked in my case.:

      – name: Send Test Notification


      url: “https://ntfy-url/Topic”
      method: POST

      Title: “Notification Title”
      Authorization: “Bearer this-is-your-token

      body: “Filler text”
      force_basic_auth: true

      Remove the username and password and add “Authorization” below the “headers”.
      I will update the post to make it more readable.

      1. NeoPlayer

        So it was an indentation problem. Thanks a lot! Is working perfectly now.

Leave a Reply