PrivateBin NGINX Access Log Dashboard

ยท 5 min read
PrivateBin NGINX Access Log Dashboard

The PrivateBin NGINX JSON Access Log dashboard provides statistics for tracking user activity on your PrivateBin instance. You can monitor the creation and access of pastes.

It's important to note that PrivateBin does not generate application logs, so we rely solely on the NGINX Access Log. While this log doesn't contain extensive audit data, it does provide sufficient information to display certain insights.

If you're unfamiliar with PrivateBin, you can learn more about it by visiting this PrivateBin deployment guide.

To set up the required components, please ensure you have the following:

What You Need Ansible Role for Deployment
Grafana Ansible Grafana Collection
Grafana Loki Ansible Loki Role
Grafana Promtail Ansible Promtail Role
PrivateBin Ansible PrivateBin Role

Assuming you have fulfilled the above requirements, let's proceed from this point.

Dashboard Features

Before I list the features, I want to say thank you to the creators of the NGINX JSON Access Log dashboard that inspired me. You might be wondering why I decided to create another dashboard.
Well, the answer is straightforward: While the dashboard I mentioned earlier provides an overview of NGINX statistics, it doesn't focus directly on monitoring the "created" or "accessed" paste requests. My dashboard is designed specifically for that purpose.

With my dashboard, you can easily keep an eye on important paste-related information, such as:

Panel Title Panel Type Description
Total Created Paste Stat Total number of pastes created
Total Requested Paste Stat Total number of pastes requested
Total Created Paste by Unique IP Stat Total number of unique IP addresses that created pastes
Total Requested Paste by Unique IP Stat Total number of unique IP addresses that requested pastes
Paste - Total Body Bytes Send Stat Total number of body bytes sent in POST and GET requests
Total Request 4xx Stat Total number of requests with a 4xx status code (including all server requests, not just pastes)
Total Request 5xx Stat Total number of requests with a 5xx status code (including all server requests, not just pastes)
Paste - Privatebin Recent Log Logs Recent logs focused on important information (using lineformat)
Paste - GET/POST Status Code Time Series Graphical representation of GET/POST request status codes over time
Paste - Body Bytes Send Time Series Graphical representation of body bytes sent in POST and GET requests over time
Paste - Top 10 User Agents Table Table showing the top 10 user agents
Paste - Top 10 Requested Paste by IP and Country Table Table displaying the top 10 requested pastes by IP addresses and countries
Paste - Top 15 Requested Paste Table Table showing the top 15 requested pastes
Paste - Top 15 Referer Table Table highlighting the top 15 referrers
Top 10 Requested Paste by Country Table Table presenting country codes and the number of requested pastes per country

I used a few emojis to make it cleaner and easier to recognize what is what in the log panel:

โœ‰๏ธ POST ๐Ÿ“‹/ ๐ŸŒ<COUNTRY> <IP> ๐Ÿ†”<CF-RAY-ID> ๐Ÿ’ป<USER_AGENT>
๐Ÿ“œ GET ๐Ÿ“‹/?pasteid=7dcb133aee836cdf ๐ŸŒ<COUNTRY> <IP> ๐Ÿ†”<CF-RAY-ID> ๐Ÿ’ป<USER_AGENT>

These features will help you get valuable insights into the activity on your PrivateBin instance, with a specific focus on paste-related data.

PrivateBin Logging Configuration

To ensure smooth Loki parsing, the best approach is to use the JSON format for NGINX Access Log. This blog post is focused on deploying my PrivateBin Ansible Role, so I will include the variables that allow you to configure the access_log.conf for PrivateBin NGINX directly. Keep in mind that you will also need to update variables related to volumes, as you must mount this file.

  • Extend volume-related variable:
- "{{ privatebin_dir }}/access_log.conf:/etc/nginx/http.d/access_log.conf:ro,Z"
  • Below is an example of how you can extend the variable list with access_log.conf for NGINX:
privatebin_custom_conf:
  - filename: "access_log.conf"
    raw_content: |
      log_format log-json escape=json
        '{'
          '"time_local":"$time_local",'
          '"remote_addr":"$remote_addr",'
          '"status": "$status",'
          '"msec": "$msec",'
          '"bytes_sent":"$bytes_sent",'
          '"body_bytes_sent":"$body_bytes_sent",'
          '"request":"$request",'
          '"request_time":"$request_time",'
          '"request_method":"$request_method",'
          '"request_uri":"$request_uri",'
          '"request_length":"$request_length",'
          '"host":"$http_host",'
          '"referer":"$http_referer",'
          '"user_agent":"$http_user_agent",'
          '"x_forwarded_for":"$http_x_forwarded_for",'
          '"x_forwarded_proto":"$http_x_forwarded_proto",'
          '"connection":"$connection",'
          '"accept_encoding":"$http_accept_encoding",'
          '"accept_language":"$http_accept_language",'
          '"accept":"$http_accept",'
          '"cf_ray":"$http_cf_ray",'
          '"cf_connecting_ip":"$http_cf_connecting_ip",'
          '"cf_ipcountry":"$http_cf_ipcountry",'
          '"sec_fetch_site":"$http_sec_fetch_site",'
          '"sec_fetch_mode":"$http_sec_fetch_mode",'
          '"sec_fetch_dest":"$http_sec_fetch_dest"'
        '}';
        access_log /dev/stdout log-json;

From this point, journald will log our access.log in JSON format. It's important to note that this dashboard relies on Cloudflare headers. If you don't use Cloudflare, you must change, for example, cf_connecting_ip and cf_ipcountry in all panels or simply do the following in above configuration:

'"cf_connecting_ip":"$remote_addr",'
'"cf_ipcountry":"$geoip_country_code",'

Promtail Configuration

Below is an extracted configuration snippet for Promtail, specifically tailored for PrivateBin:

scrape_configs:
  - job_name: journal-container-json
    journal:
      json: false
      max_age: 1h
      labels:
        instance: privatebin.voidquark.com
        env: dev
        job: containers-json
    relabel_configs:
    - source_labels:
      - __journal_container_name
      target_label: container
    - source_labels:
      - __journal_container_name
      action: keep
      regex: privatebin

This configuration ensures that only containers with the name privatebin will send logs to Loki. Any logs from other containers will be discarded if their names don't match. I strongly recommend setting the instance and env labels as they help you identify which host is actually hosting this container. This is particularly useful if you are running multiple PrivateBin instances, as it allows you to select which one should display data in the PrivateBin dashboard.

PrivateBin Configuration

It's important to note that certain options must be disabled in PrivateBin config (/srv/cfg/conf.php); otherwise, the dashboard will not function properly. This issue arises because POST requests are made to the / URL both when creating a new paste and when someone create comment. Consequently, it becomes challenging to distinguish between a comment and a newly created paste in the logs. To address this, the following options should be disabled to ensure accurate tracking of newly created pastes:

[main]
  discussion = false
  opendiscussion = false

Source Code Available for Everyone


While this dashboard may not offer extensive functionality, it's still a valuable resource. It can assist you in gaining insights into the behavior of visitors who use your PrivateBin instance. It's important to note that this dashboard is specifically designed to focus on clients that accept the application/json header. As a result, it should effectively capture standard user interactions through web browsers.


Thanks for reading. I'm entering the void. ๐Ÿ›ธ โžก๏ธ ๐Ÿ•ณ๏ธ