## 1. Project Overview & Quickstart (pi-hole/index)

# index

Open-source repository pi-hole/index

### Repository Details
- **Repository:** [pi-hole/index](https://github.com/pi-hole/index)
- **Primary Language:** Code

*Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.*

## 2. Official Technical Reference & Guides (pi-hole/docs)

## File: README.md

The Pi-hole[®](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software.

## Documentation & User Guides

This repo is the source for the official [Pi-hole documentation](https://docs.pi-hole.net/).

### How to contribute

To add a new link on the navigation panel you need to edit the `mkdocs.yml` file in the root of the repo. There is a guide for building the navbar [on the mkdocs wiki](https://www.mkdocs.org/user-guide/configuration/#nav)

To add a new document or guide.

- Navigate to the directory where it will be hosted.
    E.g. guides are in `docs/guides`
- Create the file using a URL friendly filename.
    E.g. `docs/guides/url-friendly.md`
- Edit your document using Markdown, there are loads of resources available for the correct syntax.

### Testing your changes

When working on this repo, it is advised that you review your changes locally before committing them. The `mkdocs serve` command can be used to live preview your changes (as you type) on your local machine.

Please make sure you fork the repo and change the clone URL in the example below for your fork:

- Linux Mint / Ubuntu 20.04 LTS / 23.10 and later:
    - Preparations (only required once):

    ```bash
    git clone https://github.com/YOUR-USERNAME/docs
    cd docs
    sudo apt install python3-pip python3-venv
    python3 -m venv .venv
    source .venv/bin/activate
    pip3 install -r requirements.txt
    ```

    - Enter the virtual environment (if exited):

    ```bash
    source .venv/bin/activate
    ```

    - Running the docs server:

    ```bash
    mkdocs serve --dev-addr 0.0.0.0:8000
    ```

- Fedora Linux instructions (tested on Fedora Linux 28):
    - Preparations (only required once):

    ```bash
    git clone https://github.com/YOUR-USERNAME/docs
    cd docs
    pip install --user -r requirements.txt
    ```

    - Running the docs server:

    ```bash
    mkdocs serve --dev-addr 0.0.0.0:8000
    ```

- Docker instructions:
    - One-shot run:

    ```bash
    docker run -v `pwd`:/opt/app/ -w /opt/app/ -p 8000:8000 -it nikolaik/python-nodejs:python3.7-nodejs16 \
      sh -c "pip install --user -r requirements.txt && \
      /root/.local/bin/mkdocs build && \
      npm ci && \
      npm test && \
      /root/.local/bin/mkdocs serve --dev-addr 0.0.0.0:8000"
    ```

After these commands, the current branch is accessible through your favorite browser at <http://localhost:8000>

[](https://www.netlify.com/)

---

## File: docs/api/auth.md

Authentication is required for most API endpoints. The Pi-hole API uses a session-based authentication system. This means that you will not be able to use a static token to authenticate your requests. Instead, you will be given a session ID (SID) that you will have to use. If you didn't set a password for your Pi-hole, you don't have to authenticate your requests.

To get a session ID, you will have to send a `POST` request to the `/api/auth` endpoint with a payload containing your password. Note that it is also possible to use an application password instead of your regular password, e.g., if you don't want to put your password in your scripts or if you have 2FA enabled for your regular password. One application password can be generated in the web interface on the settings page.


???+ example "Authentication with password"

    === "bash / cURL"

        ```bash
        curl -k -X POST "https://pi.hole/api/auth" --data '{"password":"your-password"}'
        ```

    === "Python 3"

        ```python
        import requests

        url = "https://pi.hole/api/auth"
        payload = {"password": "your-password"}

        response = requests.request("POST", url, json=payload, verify=False)

        print(response.text)
        ```

    === "JavaScript (plain)"

        ```javascript
        var data = JSON.stringify({"password":"your-password"});
        var xhr = new XMLHttpRequest();

        xhr.addEventListener("readystatechange", function () {
          if (this.readyState === this.DONE) {
            console.log(this.responseText);
          }
        });
        ```

    === "JavaScript (jQuery)"

        ```javascript
        $.ajax({
          url: "https://pi.hole/api/auth",
          type: "POST",
          data: JSON.stringify({"password":"your-password"}),
          dataType: "json",
          contentType: "application/json"
        }).done(function(data) {
          console.log(data);
        }).fail(function(xhr, status, error) {
          console.log(error);
        });
        ```

    === "C"

        ```c
        #include <stdio.h>
        #include <stdlib.h>
        #include <curl/curl.h>

        int main(void)
        {
          CURL *curl;
          CURLcode res;

          curl_global_init(CURL_GLOBAL_ALL);

          curl = curl_easy_init();
          if(curl) {
            curl_easy_setopt(curl, CURLOPT_URL, "https://pi.hole/api/auth");
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"password\":\"your-password\"}");
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
            curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");

            struct curl_slist *headers = NULL;
            headers = curl_slist_append(headers, "Content-Type: application/json");
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

            res = curl_easy_perform(curl);

            curl_easy_cleanup(curl);
          }

          curl_global_cleanup();

          return 0;
        }
        ```

    **Parameters**

    Specify either your password or an application password as `password` in the payload.

???+ success "Success response"

    Response code: `HTTP/1.1 200 OK`

    ```json
    {
      "session": {
        "valid": true,
        "totp": false,
        "sid": "vFA+EP4MQ5JJvJg+3Q2Jnw=",
        "csrf": "Ux87YTIiMOf/GKCefVIOMw=",
        "validity": 300
      },
      "took": 0.0002
    }
    ```

???+ failure "Error response"

    Response code: `HTTP/1.1 401 - Unauthorized`

    ```json
    {
      "error": {
        "key": "unauthorized",
        "message": "Unauthorized",
        "hint": null
      },
      "took": 0.003
    }
    ```

On success, this will return a JSON object containing the session ID (SID) and the time until your session expires (validity in seconds).
You can use this SID from this point to authenticate your requests to the API.

## Use the SID to access API endpoints

Once you have a valid SID, you can use it to authenticate your requests. You can do this in four different ways:

1. In the request URI: `http://pi.hole/api/info/version?sid=9N80JpYyHRBX4c5RW95%2Fyg%3D` (your SID needs to be URL-encoded)
2. In the payload of your request: `{"sid":"vFA+EP4MQ5JJvJg+3Q2Jnw="}`
3. In the `X-FTL-SID` header: `X-FTL-SID: vFA+EP4MQ5JJvJg+3Q2Jnw=`
4. In the `sid` cookie: `Cookie: sid=vFA+EP4MQ5JJvJg+3Q2Jnw=`

Note that when using cookie-based authentication, you will also need to send a `X-FTL-CSRF` header with the CSRF token that was returned when you authenticated. This is to prevent a certain kind of identity theft attack the Pi-hole API is immune against.

???+ example "Authentication with SID"

    === "bash / cURL"

        ```bash
        # Example: Authentication with SID in the request URI
        curl -k -X GET "https://pi.hole/api/dns/blocking?sid=vFA+EP4MQ5JJvJg+3Q2Jnw="
        ```

    === "Python 3"

        ```python
        # Example: Authentication with SID in the request header
        import requests

        url = "https://pi.hole/api/dns/blocking"
        payload = {}
        headers = {
          "X-FTL-SID": "vFA+EP4MQ5JJvJg+3Q2Jnw=",
          "X-FTL-CSRF": "Ux87YTIiMOf/GKCefVIOMw="
        }

        response = requests.request("GET", url, headers=headers, data=payload, verify=False)

        print(response.text)
        ```

    === "JavaScript (plain)"

        ```javascript
        var data = null;
        var xhr = new XMLHttpRequest();

        xhr.addEventListener("readystatechange", function () {
          if (this.readyState === this.DONE) {
            console.log(this.responseText);
          }
        });

        xhr.open("GET", "https://pi.hole/api/dns/blocking?sid=vFA+EP4MQ5JJvJg+3Q2Jnw=");
        xhr.setRequestHeader("X-FTL-CSRF", "Ux87YTIiMOf/GKCefVIOMw=");
        xhr.send(data);
        ```

    === "JavaScript (jQuery)"

        ```javascript
        $.ajax({
          url: "https://pi.hole/api/dns/blocking",
          type: "GET",
          data: null,
          dataType: "json",
          contentType: "application/json",
          headers: {
            "X-FTL-SID": "vFA+EP4MQ5JJvJg+3Q2Jnw=",
            "X-FTL-CSRF": "Ux87YTIiMOf/GKCefVIOMw="
          }
        }).done(function(data) {
          console.log(data);
        }).fail(function(xhr, status, error) {
          console.log(error);
        });
        ```

    **Parameters**

    Specify the SID in one of the four ways described above.

    **Headers**

    If you use cookie-based authentication, you will also need to send a `X-FTL-CSRF` header with the CSRF token that was returned when you authenticated.

## Authentication with 2FA

If you have 2FA enabled for your Pi-hole, you will need to provide a TOTP token in addition to your password. You can generate this token using any TOTP app, e.g., [Google Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2) or [Authy](https://authy.com/download/). The Pi-hole API will return a `totp` flag in the response to indicate whether 2FA is enabled on your Pi-hole.

???+ example "Authentication with 2FA"

    === "bash / cURL"

        ```bash
        curl -k -X POST "https://pi.hole/api/auth" --data '{"password":"your-password", "totp":123456}'
        ```

    === "Python 3"

        ```python
        import requests

        url = "https://pi.hole/api/auth"
        payload = {
          "password": "your-password",
          "totp": 123456
        }

        response = requests.request("POST", url, json=payload, verify=False)

        print(response.text)
        ```

    === "JavaScript (plain)"

        ```javascript
        var data = JSON.stringify({"password":"your-password", "totp":123456});
        var xhr = new XMLHttpRequest();

        xhr.addEventListener("readystatechange", function () {
          if (this.readyState === this.DONE) {
            console.log(this.responseText);
          }
        });
        ```

    === "JavaScript (jQuery)"

        ```javascript
        $.ajax({
          url: "https://pi.hole/api/auth",
          type: "POST",
          data: JSON.stringify({"password":"your-password", "totp":123456}),
          dataType: "json",
          contentType: "application/json"
        }).done(function(data) {
          console.log(data);
        }).fail(function(xhr, status, error) {
          console.log(error);
        });
        ```

    **Parameters**

    Specify your password as `password` and the TOTP token you got from your TOTP app as `totp` in the payload.

## Handling Authentication Errors

When authenticating, it's possible to encounter errors. These can occur due to various reasons such as incorrect password, server issues, or network problems. Here's how you can handle these errors:

When you send a `POST` request to the `/api/auth` endpoint, the server will respond with a status code. If the authentication is successful, you will receive a `200 OK` status code. If there's an error, you will receive a different status code. Here are some common ones:

- `400 Bad Request`: This usually means that the JSON data in the request is not formatted correctly or the required fields are missing.
- `401 Unauthorized`: This means that the password provided is incorrect.
- `500 Internal Server Error`: This indicates that something went wrong on the server side (e.g., a system running out of memory).

In addition to the status code, the server will also return a JSON object with more information about the error, e.g.,

???+ failure "Error response"

    Response code: `HTTP/1.1 400 - Bad Request`

    ```json
    {
      "error": {
        "key": "bad_request",
        "message": "No password found in JSON payload",
        "hint": null
      },
      "took": 0.0001
    }
    ```

    or

    ```json
    {
      "error": {
        "key": "bad_request",
        "message": "Field password has to be of type 'string'",
        "hint": null
      },
      "took": 0.0003
    }
    ```

## Session Expiration

The session ID (SID) has a limited lifespan. Each successful request to the API will extend the lifespan of the SID. However, if there is a prolonged period of inactivity, the SID will expire and you will need to re-authenticate to obtain a new one. The timeout until the SID expires is configurable via a config setting.

## Logging Out

To end your session before the SID expires, you can send a `DELETE` request to the `/api/auth` endpoint. This will invalidate your current SID, requiring you to login again for further requests.

???+ example "Logout"

    === "bash / cURL"

        ```bash
        # Example: Logout with SID in the request URI
        curl -k -X DELETE "https://pi.hole/api/auth?sid=vFA+EP4MQ5JJvJg+3Q2Jnw="
        ```

    === "Python 3"

        ```python
        # Example: Logout with SID in the request header
        import requests

        url = "https://pi.hole/api/auth"
        payload = {}
        headers = {
          "X-FTL-SID": "vFA+EP4MQ5JJvJg+3Q2Jnw="
        }

        response = requests.request("DELETE", url, headers=headers, data=payload, verify=False)

        print(response.text)
        ```

    === "JavaScript (plain)"

        ```javascript
        var data = null;
        var xhr = new XMLHttpRequest();

        xhr.addEventListener("readystatechange", function () {
          if (this.readyState === this.DONE) {
            console.log(this.responseText);
          }
        });

        xhr.open("DELETE", "https://pi.hole/api/auth?sid=vFA+EP4MQ5JJvJg+3Q2Jnw=");
        xhr.send(data);
        ```

    === "JavaScript (jQuery)"

        ```javascript
        $.ajax({
          url: "https://pi.hole/api/auth",
          type: "DELETE",
          data: null,
          dataType: "json",
          contentType: "application/json",
          headers: {
            "X-FTL-SID": "vFA+EP4MQ5JJvJg+3Q2Jnw="
          }
        }).done(function(data) {
          console.log(data);
        }).fail(function(xhr, status, error) {
          console.log(error);
        });
        ```

    **Parameters**

    Specify the SID in one of the four ways described above.

???+ success "Success response"

    Response code: `HTTP/1.1 204 - No Content`

    No content

Remember, it's important to manage your sessions properly to maintain the security of your Pi-hole API.

## Rate Limiting

The Pi-hole API implements a rate-limiting mechanism to prevent abuse. This means that you can try a certain number of login attempts per second. If you exceed this limit, you will receive a `429 Too Many Requests` response.

## Limited number of concurrent sessions

The Pi-hole API only allows a limited number of concurrent sessions. This means that if you try to login with a new session while the maximum number of sessions is already active, you may be denied access. This is to prevent abuse and resource exhaustion. In case you hit this limit, please make sure to logout from your sessions when you don't need them anymore as this will free up API slots for future requests. Unused sessions will be automatically terminated after a certain amount of time.

## Security Implications of Session-Based Authentication

Session-based authentication, while convenient and widely used, does have several security implications that Pi-hole addresses in the following ways:

1. **Session Hijacking**: If an attacker manages to steal a user's session ID, they can impersonate that user for the duration of the session. This is mitigated indirectly by the methods #2-#4 below and also by binding sessions to the client's IP address. Be aware that this may cause issues if your IP address changes during the session's lifetime (e.g., a device reconnecting to a different WiFi network).

2. **Cross-Site Scripting (`XSS`)**: XSS attacks can be used to steal session IDs if they are stored in JavaScript-accessible cookies. Pi-hole's session cookie is not accessible to JavaScript (`HttpOnly`), so this is not a concern.

3. **Cross-Site Request Forgery (`CSRF`)**: In a CSRF attack, an attacker tricks a victim into performing actions on their behalf. This is mitigated by using CSRF tokens or implementing the `SameSite` attribute for the session cookie.

4. **Session Fixation**: In this attack, an attacker provides a victim with a session ID, and if the victim authenticates with that session ID, the attacker can use it to impersonate the victim. This is mitigated by ever reusing session IDs but always regenerating them for each new session. Pi-hole sources the session ID from a cryptographically secure random number generator to ensure that it is unique, unpredictable, and safe.

5. **Idle Sessions**: If a session remains open for a long time without activity, it can be hijacked by an attacker. This is mitigated by both the high security of the generated SIDs, making them basically non-bruteforceable, and by the fact that the session ID is bound to the client's IP address. This means that an attacker would not only have to be on the same network as the victim but even be on the same machine to hijack their session. This is furthermore mitigated by implementing session timeouts.

Remember, no security measure is foolproof, but by understanding the potential risks and the multiple layers of defense your Pi-hole implemented against these risks, you can make an informed decision about how to use the Pi-hole API securely in the context of your own scripts. Always use the secure transmission method (HTTPS) offered by your Pi-hole to access the API. The strong encryption will prevent attackers from eavesdropping on your requests and makes stealing your session ID basically impossible.

{!abbreviations.md!}

---

## File: docs/api/index.md

The Pi-hole API is organized around [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer). Our API has predictable resource-oriented URLs, accepts and returns reliable UTF-8 [JavaScript Object Notation (JSON)-encoded](http://www.json.org/) data for all API responses, and uses standard HTTP response codes and verbs.

Most (but not all) endpoints require authentication. API endpoints requiring authentication will fail with code `401 Unauthorized` if no key is supplied. See [API Reference: Authentication](auth.md) for details.

## Accessing the API documentation

The entire API is documented at http://pi.hole/api/docs and self-hosted by your Pi-hole to match 100% the API version your local Pi-hole has. Using this locally served API documentation is preferred. In case you don't have Pi-hole installed yet, you can also check out the documentation for all branches online, e.g., [Pi-hole API documentation](https://ftl.pi-hole.net/master/docs/) (branch `master`). Similarly, you can check out the documentation for a specific other branches by replacing `master` with the corresponding branch name. 

## API endpoints

An overview of all available endpoints is available at the API documentation page. The endpoints are grouped by their functionality.

## JSON response

The form of replies to successful requests strongly depends on the selected endpoint, e.g.,


???+ success "Example reply: Success"

    Resource: `GET /api/dns/blocking`

    Response code: `HTTP/1.1 200 OK`

    ```json
    {
      "blocking": true
    }
    ```

    **Reply type**

    Object or Array

    **Fields**

    Depending on the particular endpoint


In contrast, errors have a uniform, predictable style to ease their programmatic treatment:


???+ failure "Example reply: Error (unauthorized access)"

    Resource: `GET /api/domains`

    Response code: `HTTP/1.1 401 Unauthorized`

    ```json
    {
        "error": {
            "key": "unauthorized",
            "message": "Unauthorized",
            "hint": null
        }
    }
    ```

    **Reply type**

    Object

    **Fields**

    ??? info "Key describing the error (`"key": string`)"
        This string may be used for internal categorization of error types

        Examples for `key` are:

        - `bad_request`

            Possible reason: Payload is invalid for this endpoint

        - `database_error`

            Possible reason: Failed to read/write to the database

    ??? info "Human-readable description of the error (`"message": string`)"
        This string may be shown to the user for troubleshooting

        Examples for `messages` are:

        - `Could not read domains from database table`

            Possible reason: Database is not readable

        - `No request body data`

            Possible reason: Payload is empty

        - `Invalid request body data`

            Possible reason: Payload is not valid JSON

        - `No "domain" string in body data`

            Possible reason: The required field `domain` is missing in the payload

    ??? info "Additional data (`"hint": [object|string|null]`)"

        The field `hint` may contain a JSON object or string. Its content depends on the error itself and may contain further details such as the interpreted user data. If no additional hint is available for this endpoint, `null` is returned.

        Examples for a failed request with `hint` being set is (domain is already on this list):

        ```json
        {
            "error":  {
                "key": "database_error",
                "message": "Could not add to gravity database",
                "hint": {
                    "argument": "abc.com",
                    "enabled": true,
                    "sql_msg": "UNIQUE constraint failed: domainlist.domain, domainlist.type"
                }
            }
        }
        ```


## HTTP methods used by this API

Each HTTP request consists of a method that indicates the action to be performed on the identified resource. The relevant standard is [RFC 2616](https://tools.ietf.org/html/rfc2616). Though, RFC 2616 has been very clear in differentiating between the methods, complex wordings are a source of confusion for many users.

Pi-hole's API uses the methods like this:

Method   | Description
---------|------------
`GET`    | **Read** from resource
`POST`   | **Create** a resource
`PATCH`  | **Update** existing resource
`PUT`    | **Create or Replace** a resource
`DELETE` | **Delete** existing resource


??? info "Summarized details from [RFC 2616, Scn. 9](https://tools.ietf.org/html/rfc2616#section-9) (`GET/POST/PUT/DELETE`) and [RFC 2068, Scn. 19.6.1.1](https://datatracker.ietf.org/doc/html/rfc2068#section-19.6.1.1) (`PATCH`)"
    ### `GET`

    The `GET` method means retrieve whatever information (in the form of an entity) that is identified by the URI.

    As `GET` requests do not change the state of the resource, these are said to be **safe methods**. Additionally, `GET` requests are **idempotent**, which means that making multiple identical requests must produce the same result every time until another method (`POST` or `PUT`) has changed the state of the resource on the server.

    For any given HTTP `GET`, if the resource is found on the server, then the API returns HTTP response code `200 (OK)` – along with the response body.

    In case a resource is NOT found on server, then the API returns HTTP response code `404 (Not found)`. Similarly, if it is determined that `GET` request itself is not correctly formed then API will return HTTP response code `400 (Bad request)`.

    ### `POST`

    Use `POST` APIs to **create new subordinate records**, e.g., a file is subordinate to a directory containing it or a row is subordinate to a database table. W`POST` methods are used to create a new resource into the collection of resources.

    If a resource has been created on the origin server, the response will be `201 (Created)`.
    Not all action performed using the `POST` method will result in a resource that can be identified by a URI. In such a case, either `200 (OK)` or `204 (No Content)` is the appropriate response status, depending on whether or not the response includes an entity that describes the result.

    Note that `POST` is **neither safe nor idempotent**, and invoking two identical `POST` requests typically results in an error.

    ### `PUT`

    Use `PUT` primarily to **update existing records** (if the resource does not exist, the API will typically create a new record for it). If a new record has been added at the given URI, or an existing resource is modified, either the `200 (OK)` or `204 (No Content)` response codes are sent to indicate successful completion of the request.

    ### `PATCH`

    The `PATCH` method is similar to `PUT` except that the entity contains a list of differences between the original version of the resource identified by the Request-URI and the desired content of the resource after the `PATCH`` action has been applied. The list of differences is in a format defined by the media type of the entity (e.g., "application/diff") and MUST include sufficient information to allow the server to recreate the changes necessary to convert the original version of the resource to the desired version.

    ### `DELETE`

    As the name applies, `DELETE` APIs are used to **delete records** (identified by the Request-URI).

    A successful response will be `200 (OK)` if the response includes an entity describing the status, `202 (Accepted)` if the action has not yet been enacted, or `204 (No Content)` if the action has been enacted but the response does not include an entity.

    `DELETE` operations are **idempotent**. If you `DELETE` a resource, it’s removed from the collection of resources. Repeatedly calling `DELETE` on that resource will not change the outcome – however, calling `DELETE` on a resource a second time *may* return a 404 (NOT FOUND) since it was already removed.

???+ info "Example"
    Let’s list down a few URIs and their purpose to get better understanding when to use which method:

    Method + URI | Interpretation
    ---------------------|--------------------
    `GET /api/groups` | Get all groups
    `POST /api/groups` | Create a new group
    `GET /api/groups/abc` | Get the group `abc`
    `PUT /api/groups/abc` | Update the group `abc`
    `DELETE /api/groups/abc` | Delete group `abc`


## Error handling

Pi-hole uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted, missing authentication, etc.). Codes in the `5xx` range indicate an error with Pi-hole's API (these are rare).

Some `4xx` errors that could be handled programmatically include an error code that briefly explains the error reported:

Code | Description | Interpretation
---- | ----------- | --------------
`200` | `OK` | Everything worked as expected
`201` | `Content Created` | Added a new item
`204` | `No Content` | Removed an item
`400` | `Bad Request` | The request was unacceptable, often due to a missing required parameter
`401` | `Unauthorized` | No session identity provided for endpoint requiring authorization
`402` | `Request Failed` | The parameters were valid but the request failed
`403` | `Forbidden` | The API key doesn't have permissions to perform the request
`404` | `Not Found` | The requested resource doesn't exist
`429` | `Too Many Requests` | Too many requests hit the API too quickly
`500`, `502`, `503`, `504` | `Server Errors` | Something went wrong on Pi-hole's end (These are rare)

We recommend writing code that gracefully handles all possible API exceptions. The Pi-hole API is designed to support this by standardized error messages and human-readable hints for errors.

{!abbreviations.md!}

---

## File: docs/api/tls.md

Pi-hole creates a self-signed certificate during installation. This certificate is used to encrypt the web interface and the API. While this certificate is secure, it is not trusted by your browser. This means that you will get a warning when you open the web interface or use the API like:



You can avoid this warning by either using your own secure certificate and domain or by adding the certificate to your trusted certificates.

## Adding the CA to your browser

If you want to use the self-signed certificate, you can add the automatically generated certificate authority (CA) to your browser. This will make your browser trust all certificates signed by this CA. This is the same mechanism that is used by your browser to trust certificates signed by a certificate authority like Let's Encrypt. The following instructions show how to add the CA to Firefox and Chrome. The instructions for other browsers are similar.

It is also possible to add the CA to your operating system's certificate store. This will make all applications that use the operating system's certificate store trust the CA. The instructions for this are operating system specific and are not covered here.

Note that you have to add the **CA** certificate (e.g., `/etc/pihole/tls_ca.crt`) and not the server certificate (e.g., `/etc/pihole/tls.pem`).

It is worth noting that the certificate is only valid for the domain that you had configured during certificate creation. If you access the Pi-hole web interface using a different domain, you will get a warning. This is because the certificate does not match the domain. You can either add the certificate for the other domain as well or you can create a new certificate for the other domain. You can easily create a new certificate by removing the old certificate and restarting `pihole-FTL` (e.g., `sudo rm /etc/pihole/tls* && sudo service pihole-FTL restart`). This will create a new certificate for the domain configured in `/etc/pihole/pihole.toml` (setting `webserver.domain`).


!!! warning "Security warning"
    If you add the CA to your browser, you will trust all certificates signed by this CA. By discarding the CA's private key after certificate creation, the risk of a compromised CA is minimized. However, this also means you need to create (and, in turn, add to your OS or browser) a new CA whenever you change the domain of your Pi-hole installation.

    If you want to revoke the CA certificate itself, you can simply delete it from either your operating system's trust store or the browsers you have added it to at any time.


### Firefox (tested with Firefox 121.0)

| Before | After |
| :-----: | :-----: |
|  |  |

1. Open the settings page of Firefox at [about:preferences#privacy](about:preferences#privacy)
2. Search for "Certificates"
    
3. Click on "View Certificates"
4. Select the "Authorities" tab
5. Click on "Import" and select the **CA** certificate file (e.g., `/etc/pihole/tls_ca.crt`)
6. Check "Trust this CA to identify websites"
    
7. Click on "OK"
8. Verify that the certificate has been imported correctly
    
9. Verify that the Pi-hole web interface is now trusted (no warning, secure lock icon)

If the last step did not work, make sure that you have generated the certificate correctly (you may have a mismatch between the configured domain and the domain used by Pi-hole during certificate creation). Also verify that you have imported the **CA** certificate (not the server certificate).

### Chrome (tested with Chrome 120.0)

| Before | After |
| :-----: | :-----: |
|  |  |

1. Open the settings page of Chrome at [chrome://settings/privacy](chrome://settings/privacy)
2. Navigate to "Manage certificates" in the "Security" submenu of "Privacy and security" or use the search bar
    
3. Click on "Authorities" tab
4. Click on "Import" and select the **CA** certificate file (e.g., `/etc/pihole/tls_ca.crt`)
5. Check "Trust this certificate for identifying websites"
    
6. Click on "OK"
7. Verify that the certificate has been imported correctly
    
8. Verify that the Pi-hole web interface is now trusted (no warning, secure lock icon)

If the last step did not work, see the remark below the Firefox instructions above.

### Android (tested with Android 11 and Firefox Mobile 121.1.0)

| Before | After |
| :-----: | :-----: |
|  |  |
|  |  |

1. Go to your device's settings
2. Navigate to "System Security" or "Security & location" (depending on your device)
3. Navigate to "Credential storage" or similar (depending on your device)

    

4. Choose "Install certificates from storage" or similar (depending on your device)

    

5. Select "User certificates" or similar (depending on your device)

    

6. Select the **CA** certificate file (e.g., `/etc/pihole/tls_ca.crt`) - you need to have the certificate file on your device (e.g., by copying it to your device via Bluetooth or other *secure* means)

    

7. Confirm that you want to install the certificate despite the possible security implications mentioned above

    

8. Give the certificate a meaningful name (e.g., "Pi-hole")

    

9. Verify that the certificate has been imported correctly by checking for a small success popup in the lower area of the screen

    

10. Verify that the Pi-hole web interface is now trusted (no warning, secure lock icon)

The certificate will be valid for all apps that use the Android certificate store. This includes the Firefox Mobile browser but also others such as Chrome.

#### Additional steps for recent Firefox Mobile versions (tested with Android 14 and Firefox Mobile 136.0)

On recent versions of the Firefox Android browser (this does not seem necessary with Chrome), you may need to perform the following additional steps to enable the browser to use user-defined certificates:

1. Open Firefox
2. Navigate to "Settings" and then "About Firefox"
3. Tap the Firefox logo five times to unlock the hidden menu
4. Go back to "Settings" and open "Secret Settings"
5. Enable "Use third party CA certificates"

If this still did not work, see the remark below the Firefox instructions above.

## Using your own certificate

If you want to use your own certificate, you can do so by placing the certificate and the private key in a location that can be read by user `pihole` (e.g., `/etc/pihole`) and, change the path in `/etc/pihole/pihole.toml` (setting `webserver.tls.cert`) and restart `pihole-FTL` (e.g., `sudo service pihole-FTL restart`). The certificate and the private key must be in PEM format (check automatically generated certificate for an example).

---

## File: docs/database/domain-database/groups.md

---
title: Group Management
---

Groups are defined in the `group` table and can have an optional description in addition to the mandatory name of the group.

Label | Type | Uniqueness enforced | Content
----- | ---- | ------------------- | --------
`id` | integer | Yes | Unique ID for database operations
`enabled` | boolean | No | Flag whether domains in this group should be used(`0` = disabled, `1` = enabled)
`name` | text | Yes | Mandatory group name
`description` | text | No | Optional field for arbitrary user comments

Group management is implemented using so-called linking tables. Hence, it is possible to

- associate domains (and clients!) with any number of groups,
- manage lists together with groups,
- use the same groups for denylist and allowlist domains at the same time.

The linking tables are particularly simple, as they only link group `id`s with list `id`s. As an example, we describe the `domainlist_by_group` table. The `adlist` and `client` linking tables are constructed similarly.

Label | Type | Content
----- | ---- | -------
`domainlist_id` | integer | `id` of domain in the `domainlist` table
`group_id` | integer | `id` of associated group in the `group` table

Group `Default` (`group_id` `0`) is special as it is automatically assigned to domains and clients not being a member of other groups. Each newly added client or domain gets assigned to group zero when being added.

---

## File: docs/database/domain-database/index.md

---
title: Domain Database
---

Pi-hole uses the well-known relational database management system SQLite3 for managing the various domains that are used to control the DNS filtering system. The database-based domain management has been added with Pi-hole v5.0. The ability to subscribe to external *allow*lists has been added with Pi-hole v6.0.

## Priorities

Pi-hole uses the following priorities when deciding whether to block or allow a domain:

1. Exact allowlist entries
2. Regex allowlist entries
3. Exact denylist entries
4. Subscribed allowlists
5. Subscribed denylist
6. Regex denylist entries

## Domain tables (`domainlist`)

The database stores allow-, and denylists which are directly relevant for Pi-hole's domain blocking behavior. The `domainlist` table contains all domains on the allow- and denylists. It has a few extra fields to store data related to a given domain such as the `enabled` state, the dates when the domain was added and when it was last modified, and an optional comment.

The date fields are defined as `INTEGER` fields as they expect numerical timestamps also known as *UNIX time*. The `date_added` and `date_modified` fields are initialized with the current timestamp converted to UNIX time. The `comment` field is optional and can be empty.

Pi-hole's *FTL*DNS reads the tables through the various view, omitting any disabled domains.

Label | Type | Uniqueness enforced | Content
----- | ---- | ------------------- | --------
`id` | integer | Yes | Unique ID for database operations
`type` | integer | No | `0` = exact allowlist, `1` = exact denylist, `2` = regex allowlist, `3` = regex denylists
`domain` | text | Yes | Domain
`enabled` | boolean | No | Flag whether domain should be used by `pihole-FTL`(`0` = disabled, `1` = enabled)
`date_added` | integer | No | Timestamp when domain was added
`date_modified` | integer | No | Timestamp when domain was last modified, automatically updated when a record is changed
`comment` | text | No | Optional field for arbitrary user comments, only field that is allowed to be `NULL`

## List Table (`adlist`)

The `adlist` table contains all sources for domains to be collected by `pihole -g`. Just like the other tables, it has a few extra fields to store metadata related to a given source.

Label | Type | Uniqueness enforced | Content
----- | ---- | ------------------- | --------
`id` | integer | Yes | Unique ID for database operations
`address` | text | Yes | The URL of the list
`enabled` | boolean | No | Flag whether domain should be used by `pihole-FTL`(`0` = disabled, `1` = enabled)
`date_added` | integer | No | Timestamp when domain was added
`date_modified` | integer | No | Timestamp when domain was last modified, automatically updated when a record is changed
`comment` | text | No | Optional field for arbitrary user comments
`date_updated` | integer | No | Timestamp when this list has last been updated (`pihole -g` does **not** update this timestamp when the downloaded list did not change since the last `pihole -g` run)
`number` | integer | No | Number of domains on this list
`invalid_domains` | integer | No | Number of invalid domains on this list
`status` | integer | No | `1` = List download was successful, `2` = List unchanged upstream, Pi-hole used a local copy, `3` = List unavailable, Pi-hole used a local copy, `4` = List unavailable, there is no local copy of this list available on your Pi-hole

## Gravity Tables (`gravity` and `antigravity`)

The `gravity` and `antigravity` table consists of the domains that have been processed by Pi-hole's `gravity` (`pihole -g`) command. The domains in this list are the collection of domains sourced from the configured sources (see the [List Table (`adlist`)](index.md#list-table-adlist)).

During each run of `pihole -g`, these tables are flushed and completely rebuilt from the newly obtained set of domains to be blocked or allowed.

Label | Type | Content
----- | ---- | -------
`domain` | text | Domain compiled from subscribed list referenced by `adlist_id`
`adlist_id` | integer | ID associated to subscribed list in table `adlist`

Uniqueness is enforced on pairs of (`domain`, `adlist_id`) in both tables. In other words: domains can be added multiple times, however, only when they are referencing different lists as their origins.

## Client table (`client`)

Clients are identified by their IP addresses. Each client automatically gets a unique identifier (`id`).

Label | Type | Content
----- | ---- | -------
`id` | integer | Client ID (autoincrementing)
`ip` | text | IP address of the client (IPv4 or IPv6), Uniqueness is enforced
`date_added` | integer | Timestamp when a client was added
`date_modified` | integer | Timestamp when a client was last modified, automatically updated when a record is changed
`comment` | text | Optional field for arbitrary user comments, the only field that is allowed to be `NULL`

Clients can be identified by subnets. Arbitrary subnet configurations can be specified using the widely known [Classless Inter-Domain Routing (CIDR) notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_blocks).
This allows to specify "broad clients" such as

- `192.168.1.0/24` which will match al clients in the range `192.168.1.1` to `192.168.1.255` (256 devices),
- `10.8.0.0/16` will match all clients in the range `10.8.0.1` to `10.8.255.255` (65,536 devices), and
- `192.168.100.0/22` representing the 1024 IPv4 addresses from `192.168.100.0` to `192.168.103.255`.

CIDR notation can be used for IPv6 subnets as well. The IPv6 block `2001:db8::/48` represents all IPv6 addresses from `2001:db8:0:0:0:0:0:0` to `2001:db8:0:ffff:ffff:ffff:ffff:ffff` (1,208,925,819,614,629,174,706,176 = roughly one heptillion devices).

Note that Pi-hole's implementation is more generic than what is written on the linked Wikipedia article as you can use *any* CIDR block (not only multiples of 4).

---

## File: docs/database/domain-database/recovery.md

If the `gravity.db` database has been damaged, Pi-hole offers two built-in methods to repair the database.

## Recover

Try to recover a damaged gravity database file.
Pi-hole tries to restore as much as possible from a corrupted gravity database.
Run:

```bash
pihole -g -r recover
```

## Recreate

Create a new gravity database file from scratch. This will remove your existing gravity database and create a new file from scratch. If you still have the migration backup created when migrating to Pi-hole v5.0, Pi-hole will import these files.
Run:

```bash
pihole -g -r recreate
```

---

## Force recover (not recommended)

!!! warning
    This option is meant to be a last resort. Recovery is a fragile task consuming a lot of resources and shouldn't be performed unnecessarily.

Force Pi-hole to run the recovery process even when no damage is detected.
Run:

```bash
pihole -g -r recover force
```

---

## File: docs/database/index.md

Pi-hole uses the well-known relational database management system SQLite3 both for its long-term storage of query data and for its domain management. In contrast to many other database management solutions, Pi-hole does not need a server database engine as the database engine is directly embedded in *FTL*DNS. It seems an obvious choice as it is probably the most widely deployed database engine - it is used today by several widespread web browsers, operating systems, and embedded systems (such as mobile phones), among others. Hence, it is rich in supported platforms and offered features. SQLite implements most of the SQL-92 standard for SQL and can be used for high-level queries.

Details concerning the databases, their contained tables and exemplary SQL commands allowing even complex requests to Pi-hole's databases are described on the subpages of this category.

- [Query database `/etc/pihole/pihole-FTL.db`](query-database.md)
- [Domain database `/etc/pihole/gravity.db`](domain-database/index.md)

---

## File: docs/database/query-database.md

Pi-hole *FTL*DNS uses the well-known relational database management system SQLite3 as its long-term storage of query data. In contrast to many other database management solutions, *FTL*DNS does not need a server database engine as the database engine is directly embedded in *FTL*DNS. It seems an obvious choice as it is probably the most widely deployed database engine - it is used today by several widespread web browsers, operating systems, and embedded systems (such as mobile phones), among others. Hence, it is rich in supported platforms and offered features. SQLite implements most of the SQL-92 standard for SQL and can be used for high-level queries.

The long-term query database was the first database that was added to the Pi-hole project.
We update this database periodically and on the exit of *FTL*DNS (triggered e.g. by a `service pihole-FTL restart`). The updating frequency can be controlled by the parameter `database.DBinterval` and defaults to once per minute. We think this interval is sufficient to protect against data losses due to power failure events. *FTL*DNS needs the database to populate its internal history of the most recent 24 hours. If the database is disabled, *FTL*DNS will show an empty query history after a restart.

The location of the database can be configured by the config parameter `files.database`. It defaults to `/etc/pihole/pihole-FTL.db`. If the given file does not exist, *FTL*DNS will create a new (empty) database file.

Another way of controlling the size of the long-term database is by setting a maximum age for log queries to keep using the config parameter `database.maxDBdays`. It defaults to 365 days, i.e. queries that are older than one year get periodically removed to limit the growth of the long-term database file.

The config parameter `database.DBimport` controls whether `FTL` loads information from the database on startup. It needs to do this to populate the internal data structure with the most recent history. However, as importing from the database on disk can delay FTL on very large deploys, it can be disabled using this option.

---

### Split database

You can split your long-term database by periodically rotating the database file (do this only when `pihole-FTL` is *not* running). The individual database contents can easily be merged when required.
This could be implemented by running a monthly `cron` job such as:

```bash
sudo service pihole-FTL stop
sudo mv /etc/pihole/pihole-FTL.db /media/backup/pihole-FTL_$(date +"%m-%y").db
sudo service pihole-FTL start
```

Note that DNS resolution will not be available as long as `pihole-FTL` is stopped.

### Backup database

The database can be backed up while FTL is running when using the SQLite3 Online backup method, e.g.,

```bash
sqlite3 /etc/pihole/pihole-FTL.db ".backup /home/pi/pihole-FTL.db.backup"
```

will create `/home/pi/pihole-FTL.db.backup` which is a copy of your long-term database.

---

The long-term database contains several tables:

### Query Table

Label | Type | Allowed to be empty | Content
--- | --- | ---- | -----
`id` | integer | No | autoincrement ID for the table, only used by SQLite3, not by *FTL*DNS
`timestamp` | integer | No | Unix timestamp when this query arrived at *FTL*DNS (used as index)
`type` | integer | No | Type of this query (see [Supported query types](query-database.md#supported-query-types))
`status` | integer | No | How was this query handled by *FTL*DNS? (see [Supported status types](query-database.md#supported-status-types))
`domain` | text | No | Requested domain
`client` | text | No | Requesting client (IP address)
`forward` | text | Yes | Forward destination used for this query (only set if `status == 2`)
`additional_info` | blob | Yes | Data-dependent content, see below
`reply_type` | integer | Yes | Type of the reply for this query  (see [Supported reply types](query-database.md#supported-reply-types))
`reply_time` | real | Yes | Seconds it took until the final reply was received
`dnssec` | integer | Yes | Type of the DNSSEC status for this query  (see [DNSSEC status](query-database.md#dnssec-status))
`regex_id` | integer | Yes | ID of the regex filter that matched this query (only set if blocked by a regex filter)

The `queries` `VIEW` is dynamically generated from the data actually stored in the `query_storage` table and the linking tables `domain_by_id`, `client_by_id`, `forward_by_id`, and `addinfo_by_id` (see below). The table `query_storage` will contains integer IDs pointing to the respective entries of the linking tables to save space and make searching the database faster. If you haven't upgraded for some time, the table may still contain strings instead of integer IDs.

#### Data-dependent `additional_info` field

The content and type of the `additional_info` row depends on the status of the given query. For many queries, this field is empty. You should, however, not rely on this field being empty as we may add content of any type for other status types in future releases.

##### Query blocked due to a CNAME inspection (status 9, 10, 11) {#additional_info_cname data-toc-label='Blocked CNAME'}

If a query was blocked due to a CNAME inspection (status 9, 10, 11), this field contains the domain which was the reason for blocking the entire CNAME chain (text).

##### Query influenced by a deny or allowlist entry {#additional_info_list data-toc-label='domainlist_id'}

If a query was influenced by a deny or allowlist entry, this field contains the ID of the corresponding entry in the [`domainlist`](domain-database/index.md#domain-tables-domainlist) table.

### Counters table

This table contains counter values integrated over the entire lifetime of the table

Label | Type | Allowed to be empty | Content
--- | --- | ---- | -----
`id` | integer | No | ID for the table used to select a counter (see below)
`value` | integer | No | Value of a given counter

ID | Interpretation
--- | ---
0 | Total number of queries
1 | Total number of blocked queries

### FTL table

The FTL table contains some data used by *FTL*DNS for determining which queries to save to the database. This table does not contain any entries of general interest.

### Supported query types

ID | Resource Record (a.k.a. query type)
--- | ---
1 | `A`
2 | `AAAA`
3 | `ANY`
4 | `SRV`
5 | `SOA`
6 | `PTR`
7 | `TXT`
8 | `NAPTR`
9 | `MX`
10 | `DS`
11 | `RRSIG`
12 | `DNSKEY`
13 | `NS`
14 | `OTHER` (any query type not covered elsewhere, but see note below)
15 | `SVCB`
16 | `HTTPS`

Any other query type will be stored with an offset of 100, i.e., `TYPE66` will be stored as `166` in the database (see [pi-hole/FTL #1013](https://github.com/pi-hole/FTL/pull/1013)). This is done to allow for future extensions of the query type list without having to change the database schema. The `OTHER` query type is deprecated since Pi-hole FTL v5.4 (released Jan 2021) and not used anymore. It is kept for backwards compatibility. Note that `OTHER` is still used for the [regex extension `querytype=`](../regex/pi-hole.md#only-match-specific-query-types) filter and used for all queries not covered by the above list.

### Supported status types

ID | Status | | Details
--- | --- | --- | ---
0 | Unknown | ❔ | Unknown status (not yet known)
1 | Blocked | ❌ | Domain contained in [gravity database](domain-database/index.md#gravity-tables-gravity-and-antigravity)
2 | Allowed | ✅ | Forwarded
3 | Allowed | ✅ | Replied from cache
4 | Blocked | ❌ | Domain matched by a [regex denylist](domain-database/index.md#domain-tables-domainlist) filter
5 | Blocked | ❌ | Domain contained in [exact denylist](domain-database/index.md#domain-tables-domainlist)
6 | Blocked | ❌ | By upstream server (known blocking page IP address)
7 | Blocked | ❌ | By upstream server (`0.0.0.0` or `::`)
8 | Blocked | ❌ | By upstream server (`NXDOMAIN` with `RA` bit unset)
9 | Blocked | ❌ | Domain contained in [gravity database](domain-database/index.md#gravity-tables-gravity-and-antigravity)*Blocked during deep CNAME inspection*
10 | Blocked | ❌ | Domain matched by a [regex denylist](domain-database/index.md#domain-tables-domainlist) filter*Blocked during deep CNAME inspection*
11 | Blocked | ❌ | Domain contained in [exact denylist](domain-database/index.md#domain-tables-domainlist)*Blocked during deep CNAME inspection*
12 | Allowed | ✅ | Retried query
13 | Allowed | ✅ | Retried but ignored query (this may happen during ongoing DNSSEC validation)
14 | Allowed | ✅ | Already forwarded, not forwarding again
15 | Blocked | ❌ | Blocked (database is busy) How these queries are handled can be configured (dns.replyWhenBusy)
16 | Blocked | ❌ | Blocked (special domain)*E.g. Mozilla's canary domain and Apple's Private Relay domains*  Handling can be [configured](../ftldns/configfile.md)
17 | Allowed | ✅⌛ | Replied from *stale* cache
18 | Blocked | ❌ | By upstream server (EDE 15)

### Supported reply types

ID | Reply type is
--- | ---
0 | *unknown* (no reply so far)
1 | `NODATA`
2 | `NXDOMAIN`
3 | `CNAME`
4 | a valid `IP` record
5 | `DOMAIN`
6 | `RRNAME`
7 | `SERVFAIL`
8 | `REFUSED`
9 | `NOTIMP`
10 | `OTHER`
11 | `DNSSEC`
12 | `NONE` (query was dropped intentionally)
13 | `BLOB` (binary data)

### DNSSEC status

ID | DNSSEC status is
--- | ---
0 | *unknown*
1 | `SECURE`
2 | `INSECURE`
3 | `BOGUS`
4 | `ABANDONED`
5 | `TRUNCATED`

### Linking tables

The `queries` `VIEW` reads repeating properties from linked tables to reduce both database size and search complexity. These linking tables, `domain_by_id`, `client_by_id`, `forward_by_id`, and `addinfo_by_id` all have a similar structure:

#### `domain_by_id`

Label | Type | Allowed to be empty | Content
--- | --- | --- | ---
`id` | integer | No | ID of the entry. Used by `query_storage`
`domain` | text | No | Domain name

#### `client_by_id`

Label | Type | Allowed to be empty | Content
--- | --- | --- | ---
`id` | integer | No | ID of the entry. Used by `query_storage`
`ip` | text | No | Client IP address
`name` | text | Yes | Client host name

#### `forward_by_id`

Label | Type | Allowed to be empty | Content
--- | --- | --- | ---
`id` | integer | No | ID of the entry. Used by `query_storage`
`forward` | text | No | Upstream server identifier (`#`)

#### `addinfo_by_id`

Label | Type | Allowed to be empty | Content
--- | --- | --- | ---
`id` | integer | No | ID of the entry. Used by `query_storage`
`type` | integer | No | Type of the `content` field
`content` | blob | No | Type-dependent content

Valid `type` IDs are currently

- `ADDINFO_CNAME_DOMAIN = 1`; `content` is a string (the related CNAME)
- `ADDINFO_LIST_ID = 2`; `content` is an integer (ID pointing to a domain in the [`domainlist` table](domain-database/index.md#domain-tables-domainlist) or an adlist in the [`adlist` table](domain-database/index.md#list-table-adlist))
    - `>= 0` domainlist ID
    - `-1` not set
    - `-2` adlist ID `0`, `-3` adlist ID `1`, `-4` adlist ID `2`,...

### Example for interaction with the long-term query database

In addition to the interactions the Pi-hole database API offers, you can also run your own SQL commands against the database. If you want to obtain the three most queried domains for all time, you could use

```bash
sqlite3 "/etc/pihole/pihole-FTL.db" "SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3) GROUP BY domain ORDER BY count(domain) DESC LIMIT 3"
```

which would return something like

```text
discourse.pi-hole.net|421095
www.pi-hole.net|132483
posteo.de|130243
```

showing the domain and the number of times it was found in the long-term database. Note that such a request might take a very long time for computation as the entire history of queries has to be processed for this.

---

## File: docs/docker/upgrading/index.md

# Upgrading

The standard Pi-hole customization abilities apply to this docker, but with docker twists such as using docker volume mounts to map host stored file configurations over the container defaults. However, mounting these configuration files as read-only should be avoided. Volumes are also important to persist the configuration in case you have removed the Pi-hole container which is a typical docker upgrade pattern.

!!! warning "Always Read The Release Notes!"
    Ensure you read the release notes for both the Docker release and the main Pi-hole component releases. This will help you avoid common problems due to known issues with upgrading or newly required arguments or variables. The release notes can be found in the respective repository for [Core](https://github.com/pi-hole/pi-hole/releases), [FTL](https://github.com/pi-hole/FTL/releases), [Web](https://github.com/pi-hole/web/releases) and [Docker](https://github.com/pi-hole/docker-pi-hole/releases).

## Upgrading / Repairing

!!! Note
    The normal Pi-hole functions to upgrade (`pihole -up`) or repair (`pihole -r`) are disabled within the docker container. New images will be released, and you can upgrade by replacing your old container with a fresh upgraded image, which is more in line with the 'docker way'. Long-living docker containers are not the docker way since they aim to be portable and reproducible, why not re-create them often!

### Docker Compose

Navigate to the directory in which your `docker-compose.yml` file exists and run the following commands

```bash
docker compose pull
docker compose down
docker compose up -d
```

### Docker run

```bash
docker pull pihole/pihole:latest
docker stop pihole
docker rm pihole
docker run [ ... arguments (see Getting Started) ... ]
```