# Technical Documentation: electerious/Ackee

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/electerious/Ackee)

### Overview (`README.md`)

<div align="center">

<img src="https://s.electerious.com/images/ackee/icon.png" title="Ackee" alt="Ackee logo" width="128">

# Ackee

[](https://github.com/electerious/Ackee/actions/workflows/test.yml) [](https://github.com/awesome-selfhosted/awesome-selfhosted) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CYKBESW577YWE)

Self-hosted, Node.js based analytics tool for those who care about privacy. Ackee runs on your own server, analyzes the traffic of your websites and provides useful statistics in a minimal interface.

[🌍 Website](https://ackee.electerious.com) | [🔮 Live Demo](https://demo.ackee.electerious.com) | [🧸 GraphQL Playground](https://demo.ackee.electerious.com/api)

<br/>



</div>

## 👋 Introduction

Ackee is a self-hosted analytics tool that cares about privacy. We believe that you don't need to track every aspect of your visitors. Ackee keeps tracked data anonymized to avoid that users are identifiable, while still providing helpful insights. It's the right tool for everyone who doesn't need a full-featured marketing analytics platform like Google Analytics or Matomo.

- **Self-hosted**: Ackee runs on your own server and is 100% open-source
- **Modern technologies**: Lightweight Node.js and MongoDB architecture
- **Beautiful**: Minimal and focused interface
- **No cookies**: No unique user tracking and therefore no required cookie message
- **Events**: Track button clicks, newsletter subscriptions and more
- **GraphQL API**: Fully documented GraphQL API that allows you to build new tools upon Ackee

## 🚀 Get started

Get Ackee up and running…

- […with Docker Compose](docs/Get%20started.md#with-docker-compose)
- […with Docker](docs/Get%20started.md#with-docker)
- […with Helm](docs/Get%20started.md#with-helm)
- […without Docker](docs/Get%20started.md#without-docker)
- […with Netlify](docs/Get%20started.md#with-netlify)
- […with Vercel](docs/Get%20started.md#with-vercel)
- […with Heroku](docs/Get%20started.md#with-heroku)
- […with Qovery](docs/Get%20started.md#with-qovery)
- […with Render](docs/Get%20started.md#with-render)
- […with Railway](docs/Get%20started.md#with-railway)
- […with Koyeb](docs/Get%20started.md#with-koyeb)
- […with Zeabur](docs/Get%20started.md#with-zeabur)

And configure Ackee and your server correctly…

- […with environment variables](docs/Options.md)
- […with SSL and HTTPS enabled](docs/SSL%20and%20HTTPS.md)
- […with CORS headers](docs/CORS%20headers.md)

Take a look at the [FAQ](docs/FAQ.md) if you have any questions left.

## 📚 Documentation

Documentation and guides are located in [the /docs folder](docs/). Also take a look at the [FAQ](docs/FAQ.md) if you have any questions left.

### API

Ackee features a [GraphQL API](docs/API.md) that allows you to build custom tools upon Ackee. Everything you see in the UI is made from data delivered by the API.

### Options

Ackee uses environment variables and supports `.env` files in the root of the project if you want to store all variables in one file. [Options &#187;](docs/Options.md)

## Miscellaneous

### Donate

I am working hard on continuously developing and maintaining Ackee. Please consider making a donation to keep the project going strong and me motivated.

- [Become a GitHub sponsor](https://github.com/sponsors/electerious)
- [Donate via PayPal](https://paypal.me/electerious)
- [Buy me a coffee](https://www.buymeacoffee.com/electerious)

### Articles

- [Quit Google Analytics, Self-hosted Gatsby Statistics with Ackee](https://dev.to/aleccool213/quit-google-analytics-self-hosted-gatsby-statistics-with-ackee-4011)
- [Getting Ackee up and running with Heroku 🇪🇸](https://rubenr.dev/blog/ackee-analitica-web-sencilla/)
- [Why I Self-Host My Website Analytics](https://mbuffett.com/posts/why-i-self-host-my-analytics/)
- [Beyond Google Analytics: Embrace Privacy with Ackee on Vercel](https://lev.engineer/blog/beyond-google-analytics-embrace-privacy-with-ackee-on-vercel)

### Related

- [ackee-tracker](https://github.com/electerious/ackee-tracker) - Transfer data to Ackee
- [ackee-bitbar](https://github.com/electerious/ackee-bitbar) - Ackee stats in your macOS menu bar
- [ackee-lighthouse](https://github.com/electerious/ackee-lighthouse) - Send Lighthouse reports to Ackee
- [ackee-report](https://github.com/BetaHuhn/ackee-report) - CLI tool to generate performance reports
- [gatsby-plugin-ackee-tracker](https://github.com/Burnsy/gatsby-plugin-ackee-tracker) - Gatsby plugin for Ackee
- [Soapberry](https://wordpress.org/plugins/soapberry/) - WordPress plugin for Ackee
- [Ackee-PHP](https://github.com/BrookeDot/ackee-php) - A PHP Class for Ackee
- [use-ackee](https://github.com/electerious/use-ackee) - Use Ackee in React
- [nuxt-ackee](https://github.com/bdrtsky/nuxt-ackee) - Nuxt.js module for Ackee
- [ngx-ackee-wrapper](https://github.com/oakify/ngx-ackee-wrapper) - Angular wrapper for Ackee
- [django-ackee-middleware](https://github.com/suda/django-ackee-middleware) - Django middleware for Ackee
- [gridsome-plugin-ackee](https://github.com/DenzoNL/gridsome-plugin-ackee) - Gridsome plugin for Ackee
- [vuepress-plugin-ackee](https://github.com/spekulatius/vuepress-plugin-ackee) - VuePress plugin for Ackee
- [svelte-ackee](https://github.com/gaia-green-tech/svelte-ackee) - Svelte module for Ackee
- [ackee_dart](https://github.com/marchellodev/ackee_dart) - Ackee plugin for Dart/Flutter ([pub.dev](https://pub.dev/packages/ackee_dart))
- [ackee-tracker-consent](https://www.npmjs.com/package/ackee-tracker-consent) - A consent banner to activate detailed tracking on Ackee

### Links

- [Follow Ackee on Twitter](https://twitter.com/getackee)
- [Vote for Ackee on ProductHunt](https://www.producthunt.com/posts/ackee)

---

### Overview (`docs/http/README.md`)

# HTTP Files

This folder contains `.http` files for manually testing the Ackee GraphQL API. These files can be used with HTTP clients that support the `.http` file format, such as the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension for VS Code or IntelliJ IDEA's built-in HTTP client.

## Files

| File                     | Description                                                                                         |
| ------------------------ | --------------------------------------------------------------------------------------------------- |
| `tokens.http`            | Login (create token) and logout (delete token)                                                      |
| `domains.http`           | Create, read, update, and delete domains                                                            |
| `records.http`           | Create and update tracking records                                                                  |
| `events.http`            | Create, read, update, and delete events                                                             |
| `actions.http`           | Create and update event actions                                                                     |
| `permanent-tokens.http`  | Create, read, update, and delete permanent tokens                                                   |
| `facts.http`             | Fetch domain facts (active visitors, views, durations)                                              |
| `domain-statistics.http` | Domain statistics: browsers, devices, durations, languages, pages, referrers, sizes, systems, views |
| `event-statistics.http`  | Event statistics: chart and list                                                                    |

## Usage

1. Start by running the `createToken` request in `tokens.http` to obtain an access token.
2. When prompted for `accessToken`, enter the token `id` from the login response.
3. Use the token for all authenticated requests.

Requests that create tracking data (`records.http`, `actions.http`) do not require authentication, as they represent client-side tracking calls.

---

### API (`docs/API.md`)

# API

Ackee features a GraphQL API that allows you to build custom tools upon Ackee. Everything you see in the UI is made from data delivered by the API.

Here are a few resources to help you get started with GraphQL:

- https://graphql.org/learn/
- https://www.howtographql.com

## Playground

Ackee uses Apollo Sandbox, an interactive, in-browser GraphQL IDE. It allows you to play and experiment with the API of Ackee.

Simply run Ackee with `NODE_ENV` set to `development` and visit the `/api` playground in your browser. You can do so by adding `NODE_ENV=development` to the environment of your `docker-compose.yml` or by using `npm run dev`. Only use this mode on your local machine as Ackee runs slower when in development mode.

Try the [🔮 live playground](https://demo.ackee.electerious.com/api) of the Ackee demo.

## Authentication

Modifying domains or receiving aggregated data is only possible once you're authenticated. Only the creation of new data is possible without a token.

### Creating a token

The following mutation returns a new token `id` that should be used for authentication. This is what happens in the UI when you submit your username and password.

```graphql
mutation createToken($input: CreateTokenInput!) {
  createToken(input: $input) {
    payload {
      id
    }
  }
}
```

```json
{
  "input": {
    "username": "admin",
    "password": "123456"
  }
}
```

The token is valid for one day and will be renewed on every request made with it. You can modify the TTL (time to live) in [the options](Options.md#ttl). [Create a permanent token](#creating-a-permanent-token) if you need a token that doesn't expire.

### Use a token

Protected queries and mutations need to include the `Authorization` HTTP header. Replace `tokenId` with the token `id` from the previous step.

```json
{
  "Authorization": "Bearer tokenId"
}
```

### Creating a permanent token

Permanent tokens don't expire and are perfect for tools that run periodically in the background. In this case you want a token that is always valid so you don't have to enter your credentials over and over again.

You can create permanent tokens in the settings of Ackee or via the API. Creating a permanent token requires a valid [Authorization header](#use-a-token). This means that you can only create permanent tokens with a valid (non-permanent) token.

```graphql
mutation createPermanentToken($input: CreatePermanentTokenInput!) {
  createPermanentToken(input: $input) {
    payload {
      id
    }
  }
}
```

```json
{
  "input": {
    "title": "iOS widget"
  }
}
```

### Use a permanent token

Using a permanent token is the same as [using a (non-permanent) token](#use-a-token).

## Time Zone

It's sometimes necessary to know the time zone of the user to accurately group stats by day, month or year. We therefore recommend to include a `Time-Zone` header when requesting data. The time zone should be formatted as a [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).

The time zone of the server will be used as a fallback.

```json
{
  "Time-Zone": "Europe/Berlin"
}
```

## Queries

Queries are used to receive data. Here are a few examples.

- [Get all domains](#get-all-domains)
- [Get a specific domain](#get-a-specific-domain)
- [Get facts of domains](#get-facts-of-domains)
- [Get statistics of domains](#get-statistics-of-domains)
- [Get events](#get-events)

### Get all domains

```graphql
query getDomains {
  domains {
    id
    title
  }
}
```

### Get a specific domain

```graphql
query getDomain($id: ID!) {
  domain(id: $id) {
    id
    title
  }
}
```

```json
{
  "id": "3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e"
}
```

### Get facts of domains

```graphql
query getDomainsFacts {
  domains {
    facts {
      activeVisitors
      averageViews {
        count
        change
      }
      averageDuration {
        count
        change
      }
      viewsToday
      viewsMonth
      viewsYear
    }
  }
}
```

### Get statistics of domains

```graphql
query getDomainsStatistics {
  domains {
    statistics {
      durations(interval: DAILY) {
        id
        value
        count
      }
      views(interval: YEARLY, type: UNIQUE) {
        id
        value
        count
      }
      languages(sorting: TOP) {
        id
        value
        count
        created
      }
      browsers(sorting: TOP, type: WITH_VERSION) {
        id
        value
        count
        created
      }
      devices(sorting: TOP, type: WITH_MODEL) {
        id
        value
        count
        created
      }
      pages(sorting: TOP) {
        id
        value
        count
        created
      }
      referrers(sorting: TOP, type: WITH_SOURCE) {
        id
        value
        count
        created
      }
      sizes(sorting: TOP, type: SCREEN_RESOLUTION) {
        id
        value
        count
        created
      }
      systems(sorting: TOP, type: NO_VERSION) {
        id
        value
        count
        created
      }
    }
  }
}
```

### Get events

```graphql
query getEvents {
  events {
    id
    title
    statistics {
      chart(interval: DAILY, type: TOTAL) {
        id
        value
        count
      }
      list(sorting: TOP, type: TOTAL) {
        id
        value
        count
      }
    }
  }
}
```

## Mutations

Mutations are used to add, update or delete data. Here are a few examples.

- [Create a domain](#create-a-domain)
- [Delete a domain](#delete-a-domain)
- [Create a record](#create-a-record)
- [Create an event](#create-an-event)
- [Create an action](#create-an-action)
- [Update an action](#update-an-action)

### Create a domain

```graphql
mutation createDomain($input: CreateDomainInput!) {
  createDomain(input: $input) {
    payload {
      id
      title
    }
  }
}
```

```json
{
  "input": {
    "title": "Domain Title"
  }
}
```

### Delete a domain

```graphql
mutation deleteDomain($id: ID!) {
  deleteDomain(id: $id) {
    success
  }
}
```

```json
{
  "id": "3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e"
}
```

### Create a record

```graphql
mutation createRecord($domainId: ID!, $input: CreateRecordInput!) {
  createRecord(domainId: $domainId, input: $input) {
    payload {
      id
    }
  }
}
```

```json
{
  "domainId": "3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e",
  "input": {
    "siteLocation": "https://example.com"
  }
}
```

### Create an event

```graphql
mutation createEvent($input: CreateEventInput!) {
  createEvent(input: $input) {
    payload {
      id
      title
    }
  }
}
```

```json
{
  "input": {
    "title": "Event Title",
    "type": "TOTAL_CHART"
  }
}
```

### Create an action

```graphql
mutation createAction($eventId: ID!, $input: CreateActionInput!) {
  createAction(eventId: $eventId, input: $input) {
    payload {
      id
    }
  }
}
```

```json
{
  "eventId": "c8865d94-9077-420f-86a0-32545bcbf61b",
  "input": {
    "key": "Action Key",
    "value": 1
  }
}
```

### Update an action

```graphql
mutation updateAction($id: ID!, $input: UpdateActionInput!) {
  updateAction(id: $id, input: $input) {
    success
  }
}
```

```json
{
  "id": "34df5a09-498f-45c1-822c-6b1f80de5f8c",
  "input": {
    "key": "Action Key",
    "value": null
  }
}
```

---

### Anonymization (`docs/Anonymization.md`)

# Anonymization

Ackee tries its best to keep tracked data anonymized. Several steps are used to avoid that users are identifiable, while still providing helpful analytics.

## User identifier

Ackee uses the IP, user-agent and domainId to identify a user. All information will be hashed together with a [salt](<https://en.wikipedia.org/wiki/Salt_(cryptography)>) that changes daily. The final hash is called `clientId`.

The daily salt is never stored anywhere. It avoids that database backups can be used to stick data together to reconstruct the browsing history of a user.

Ackee also removes personal data from previous records when a new record with an existing identification gets added. This way the user identifier and other identifiable data is only stored once in the database. Or with other words: Ackee forgets who you are as soon as it sees you, again. It's not possible to reconstruct a browsing history, even on a daily basis.

## Personal data

Ackee won't track personal information by default, but it has the ability to do so in a privacy focused way. Our recommendation:

1. Ask the user for permission to track personal data and set a cookie to remember their decision
2. Enable `detailed` with the next page view (an option of [ackee-tracker](https://github.com/electerious/ackee-tracker))
3. Ackee now receives data considered as "personal data"

The following data is considered as "personal data":

| Name               | Description                                                                   |
| :----------------- | :---------------------------------------------------------------------------- |
| siteLanguage       | Language version of the browser.                                              |
| screenWidth        | The width of the screen in pixels.                                            |
| screenHeight       | The height of the screen in pixels.                                           |
| screenColorDepth   | The bit depth of the color palette for displaying images (in bits per pixel). |
| deviceName         | The name of the product hosting the browser.                                  |
| deviceManufacturer | The name of the product's manufacturer.                                       |
| osName             | The family of the OS.                                                         |
| osVersion          | The version of the OS.                                                        |
| browserName        | The name of the browser/environment.                                          |
| browserVersion     | The browser/environment version.                                              |
| browserWidth       | The width of the screen in pixels.                                            |
| browserHeight      | The height of the screen in pixels.                                           |

All those parameters are considered as personal data, because you could point at the tracked person when sitting in the same room (even when this is probably never the case and totally unrealistic).

`siteReferrer` and visit duration (calculated using the creation and update time of a record) is not considered as personal data as you won't be able to identify a user with this piece of information. Even when you are in the same room you would need access to the browsing history of the user.

---

### CORS Headers (`docs/CORS headers.md`)

# CORS headers

Ackee requires correct [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). [ackee-tracker](https://github.com/electerious/ackee-tracker) (the script that sends data from your sites to Ackee) won't be able to contact your server when the CORS headers aren't available or when they are configured incorrectly.

- [Reverse proxy configuration](#reverse-proxy-configuration)
- [Platforms-As-A-Service configuration](#platforms-as-a-service-configuration)

## Why?

When a site wants to send data to a different domain it needs the permissions to do so. Browsers use an OPTIONS request (preflight request) that checks to see if the CORS protocol is understood.

## Reverse proxy configuration

```
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, Time-Zone
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
```

### Origin

The `Access-Control-Allow-Origin` header only allows one domain. A wildcard (`*`) isn't recommended as it's neither a secure solution nor does it allow Ackee to ignore your own visits. Take a look at our [recommended configuration](SSL%20and%20HTTPS.md#recommended-configuration) if you want to allow requests from multiple domains or disable the `ignoreOwnVisits` option in ackee-tracker if using a wildcard is the only option for you.

```
Access-Control-Allow-Origin: https://example.com
```

### Methods

[ackee-tracker](https://github.com/electerious/ackee-tracker) needs the permission to send GET, POST, PATCH and OPTIONS requests to the server.

```
Access-Control-Allow-Methods: GET, POST, PATCH, OPTIONS
```

### Headers

The `Access-Control-Allow-Headers` header is used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.

```
Access-Control-Allow-Headers: Content-Type, Authorization, Time-Zone
```

### Credentials

The `Access-Control-Allow-Credentials` header tells the browser to include the `ackee_ignore` cookie in requests even when you're on a different (sub-)domain. This allows Ackee to ignore your own visits.

> ℹ️ Some browsers strictly block third-party cookies when Ackee runs on a different domain than the site you're visiting. Therefore, it may happen that your own visits still find their way into your statistics.

```
Access-Control-Allow-Credentials: true
```

### Max-Age

The `Access-Control-Max-Age` header tells the browser that all `Access-Control-Allow-*` headers can be cached for one hour. This minimizes the amount of preflight requests.

```
Access-Control-Max-Age: 3600
```

## Platforms-As-A-Service configuration

If you are running Ackee on a platform which handles SSL for you, you may want a quick solution for setting CORS headers instead of using a [reverse proxy](SSL%20and%20HTTPS.md).

As an environment variable, you will need to set:

```
ACKEE_ALLOW_ORIGIN=https://example.com
```

_or_

```
ACKEE_ALLOW_ORIGIN=https://example.com,https://one.example.com,https://two.example.com
```

Setting a wildcard (`*`) is also supported, but not recommended. It's neither a secure solution nor does it allow Ackee to ignore your own visits. Please disable the `ignoreOwnVisits` option in ackee-tracker if using a wildcard is the only option for you.

```
ACKEE_ALLOW_ORIGIN=*
```

---

### Enhancing Referrers (`docs/Enhancing referrers.md`)

# Enhancing referrers

## Manually setting referrers

Whenever you post a link, you can choose to add a special query parameter to the link. When the `?source=<value>` query parameter is present, Ackee will treat it as a referrer.

For example, here's what you can do when you want to send a newsletter to your subscribers. If you link to your site with `example.com`, anyone who clicks on it wouldn't show up under "Referrers".

But if you link to `example.com?source=Newsletter` anyone who clicks on that link will show "Newsletter" as the referrer source. This will allow you to see how many people have clicked on your link in the newsletter.

## Fallback referrer

The `Referer` request header contains the address of the page making the request. It's not available in all situations (e.g. when opening a link from an application). Ackee will use and show the header in the UI when no `source` parameter is available.

---

### Events (`docs/Events.md`)

# Events

With Events, you can track button clicks, newsletter subscriptions, followers and more.

## Privacy disclaimer

Ackee won't track personal information by default, but tracked events might either contain personal behavior patterns or data related to individual users. It's therefore recommended to ask the user for permission before tracking events.

## Usage

### Creating events

Create a new event in the settings of Ackee and you're ready to go. Ackee shows the id and a usage example when you click on an existing event. You can use the code to get started or by taking a look at the documentation of [ackee-tracker](https://github.com/electerious/ackee-tracker).

#### Event type

The event type specifies how Ackee will show the aggregated data in the UI. It can be changed at any time.

- **Chart with total sums**: Shows the aggregated data as a chart. Each day, month or year (depending on the chosen interval) includes the total sum of values.
- **Chart with average values**: Shows the aggregated data as a chart. Each day, month or year (depending on the chosen interval) includes the average value.
- **List with total sums**: Shows the aggregated data as a list. Each entry includes the total sum of values. Perfect when an event contains different actions (e.g. tracking choices).
- **List with average values**: Shows the aggregated data as a list. Each entry includes the average value. Perfect when an event contains different actions (e.g. tracking choices).

### Adding actions

An action should be added to an event whenever the user does what you want to track. It's similar to domains that get filled with records. You can add an action to an event [using the GraphQL API](API.md#create-an-action) or using [ackee-tracker](https://github.com/electerious/ackee-tracker). An action creation can be triggered by anything that executes JS.

Setup:

```js
import * as ackeeTracker from 'ackee-tracker'

const instance = ackeeTracker.create('https://ackee.example.com')
```

Examples:

```js
document.querySelector('#newsletter').addEventListener('click', () => {
  instance.action('1b6e20cb-7c7d-48ca-8cb6-958a55d7a9e3', {
    key: 'Subscription',
    value: 1,
  })
})
```

```js
document.querySelector('#buy').addEventListener('click', () => {
  instance.action('513a082c-2cd5-4939-b417-72da2fe1761d', {
    key: 'Checkout',
    value: parseFloat(document.querySelector('#price')),
  })
})
```

```js
setTimeout(() => {
  instance.action('b7e1e5c9-e0a5-470e-bba0-ad8a295a3cfc', {
    key: 'User on site for 5sec',
    value: 1,
  })
}, 5000)
```

### Updating actions

It's possible to update existing actions (e.g. when the user cancels a multi-step process). Doing so requires the `actionId` which can be obtained by a callback in the `.action` function.

```js
let actionId

instance.action(
  '1b6e20cb-7c7d-48ca-8cb6-958a55d7a9e3',
  {
    key: 'Subscription',
    value: 1,
  },
  (_actionId) => {
    actionId = _actionId
  },
)
```

You can then use the `actionId` to change both `key` and `value`. Even resetting an action value is possible by setting the value to `null`.

```js
instance.updateAction(actionId, {
  key: 'Subscription',
  value: null,
})
```

---

### FAQ (`docs/FAQ.md`)

# FAQ

- [Basics](#basics)
- [Features](#features)
- [Configuration](#configuration)
- [Comparison](#comparison)
- [Definitions](#definitions)

## Basics

### How does it work?

You run the Ackee server which then waits for requests through its API.

The API of Ackee accepts information about your visitors sent to it using [ackee-tracker](https://github.com/electerious/ackee-tracker). It's a small script you add to your sites, similar to the JavaScript tracking snippet provided by Google Analytics.

The interface of Ackee lets you view and analyze your tracked information.

## Features

### Can I import my nginx logs?

Ackee uses a JS snippet to aggregate data. Nginx logs won't work, but it might be possible to use the API of Ackee to build a custom import script.

### Does Ackee support multiple user-accounts?

Ackee only works with one user and it's not planned to add support for multiple user-accounts. Ackee is however lightweight enough to create one Ackee instance per user. All instances can be connected to the same MongoDB.

## Configuration

### How to reset my password?

You can change the [username and password environment variables](Options.md#username-and-password) whenever you want. Just make sure you restart Ackee after changing those.

#### How to reset my account?

There's no "account" in Ackee. Just delete your database or switch to a different one.

## Comparison

### How does Ackee compare to Matomo (Piwik)?

Matomo is more like Google Analytics in the way it works and what you can do with it. Ackee will never be a replacement if you are looking for a full-featured marketing analytics platform with tons of options and insights. Ackee tries to be less. Lightweight, easy to install and with a good balance between analytics and privacy.

Ackee is the right tool for you if Matomo offers more than you need and when you put a focus on the privacy of your users.

### How does Ackee compare to Fathom?

Ackee and Fathom are very similar. Both in the way they display data and how they process it. The biggest advantages at the moment of writing:

- Ackee is free and open-source
- Ackee offers a documented GraphQL API that lets you build upon it. The API can be used for custom import scripts or apps that display your current visitor stats in the menu bar. The possibilities are endless.
- Ackee allows you to track more than just page/site views (browser, system, etc.). This is optional and off by default, but great for people/companies that need more insights.

## Definitions

### What is a unique site view?

Each user increases the unique site view counter when visiting a domain for the first time a day.

Examples:

- User visits three pages of a domain => The count increases by one
- The same user visits your page at the end of the day => The count won't increase because the user is a returning user
- The same user visits two pages on the next day => The count increases by one because new day = new user

### What are total page views?

Each visit or reload of a page will increase the total page views counter.

Examples:

- User views three pages of a domain => The count increases by three
- User reloads the same page two times => The count increases by three (initial visit + first reload + second reload)

### Are page views unique?

No, the views aren't unique, because Ackee can't track returning users for individual pages. This is because Ackee isn't storing a browsing history of your users.

Examples:

- User visits three pages of a domain => The count of each page increases by one
- User reloads the same page two times => The count of the page increases by three (initial visit + first reload + second reload)

---

### Get Started (`docs/Get started.md`)

# Get started

Ackee is a simple analytics tool that consists of two parts:

1. A node server running on a server of your choice
2. A tracking script that talks to that server

The node server shows you the UI and receives the request from all of your sites, while the tracking script is set up on each site you want to analyze. Since normally your site and your analytics server are running on the same machine, a subdomain is recommended to proxy all requests from that subdomain to the node server. The following guides help you to set up Ackee on an infrastructure of your choice.

- [With Docker Compose](#with-docker-compose)
- [With Docker](#with-docker)
- [With Helm](#with-helm)
- [Without Docker](#without-docker)
- [With Netlify](#with-netlify)
- [With Vercel](#with-vercel)
- [With Heroku](#with-heroku)
- [With Qovery](#with-qovery)
- [With Render](#with-render)
- [With Railway](#with-railway)
- [With Koyeb](#with-koyeb)
- [With Zeabur](#with-zeabur)

## With Docker Compose

### 1. Create the configuration

Pull the project and create a `.env` file in the root to store all environment variables in one file.

```
ACKEE_USERNAME=username
ACKEE_PASSWORD=password
```

Only those two are required to run Ackee with the existing `docker-compose.yml`. Take a look at the [options](Options.md) for a detailed explanation.

### 2. Run Ackee

Run this command in the root of the project to use the predefined `docker-compose.yml`. It contains everything you need, including MongoDB and Ackee.

```sh
docker-compose up
```

> 💡 Add the `-d` flag to the Docker command to run the services in the background.

### 3. Open Ackee

Ackee will output the URL it's listening on once the server is running. Visit the URL with your browser and complete the final steps using the interface.

### 4. Get Ackee online

Ackee now runs on port `3000` and is only accessible from your local network. It's recommended to use a reverse proxy in front of Ackee. The following guides will help you through these steps.

- [SSL and HTTPS](SSL%20and%20HTTPS.md)
- [CORS headers](CORS%20headers.md)

## With Docker

### 1. Setup MongoDB

Ackee requires a running MongoDB instance. The easiest way to install MongoDB is by using [Docker](https://www.docker.com). Skip this step if you have MongoDB installed or visit the [website of MongoDB](https://www.mongodb.com) for alternative setups.

```sh
docker run -p 27017:27017 --name mongo mongo
```

For persistent storage, mount a host directory to the container directory `/data/db`, which is identified as a potential mount point in the mongo Dockerfile. When starting a new container, Docker will use the volume of the previous container and copy it to the new container, ensuring that no data gets lost.

```sh
docker run -p 27017:27017 -v /path/to/local/folder:/data/db --name mongo mongo
```

> 💡 Add the `-d` flag to the Docker command to run MongoDB in the background.

Explanation:

- `-p` makes port `27017` available at port `27017` on the host
- `-v` mounts `/path/to/local/folder` to `/data/db` of the container
- `--name` sets the container name to `mongo`
- `mongo` is the name of the image

### 2. Run Ackee

```sh
docker run -p 3000:3000 -e ACKEE_MONGODB='mongodb://mongo:27017/ackee' -e ACKEE_USERNAME='username' -e ACKEE_PASSWORD='password' --link mongo --name ackee electerious/ackee
```

> 💡 Add the `-d` flag to the Docker command to run Ackee in the background.

Explanation:

- `-p` makes port `3000` available at port `3000` on the host
- `-e` sets [environment variables](Options.md) required by Ackee
- `--link` links Ackee with the `mongo` container
- `--name` sets the container name to `ackee`
- `electerious/ackee` is the name of the image

### 3. Open Ackee

Ackee will output the URL it's listening on once the server is running. Visit the URL with your browser and complete the final steps using the interface.

### 4. Get Ackee online

Ackee now runs on port `3000` and is only accessible from your local network. It's recommended to use a reverse proxy in front of Ackee. The following guides will help you through these steps.

- [SSL and HTTPS](SSL%20and%20HTTPS.md)
- [CORS headers](CORS%20headers.md)

## With Helm

### 1. Create `values.yaml`

Based on the [default `values.yaml`](https://github.com/suda/charts/blob/main/charts/ackee/values.yaml) create your own with overrides as needed.

### 2. Deploy with `helm`

```
helm repo add suda https://suda.github.io/charts/
helm repo update
helm install ackee-release suda/ackee -n ackee -f values.yaml
```

If you're using the `ingress-nginx`, enabling the ingress will set the necessary annotations to enable CORS.

## Without Docker

### 1. Install dependencies

Ackee depends on …

- [Node.js](https://nodejs.org/en/) (v24 or newer)
- [npm](https://www.npmjs.com/)
- [MongoDB](https://www.mongodb.com) (v7 or newer)

Make sure to install and update all dependencies before you continue. The installation instructions for the individual dependencies can be found on the linked websites.

### 2. Create the configuration

Pull the project and configure Ackee using environment variables or create a `.env` file in the root of the project to store all variables in one file.

```
ACKEE_MONGODB=mongodb://localhost:27017/ackee
ACKEE_USERNAME=username
ACKEE_PASSWORD=password
```

Only those three are required to run Ackee. Take a look at the [options](Options.md) for a detailed explanation.

The [MongoDB connection string](https://docs.mongodb.com/manual/reference/connection-string/) is used to connect to your MongoDB. It should also contain the username and password of your MongoDB instance when required.

The username and password variables are used to secure your Ackee interface/API.

### 3. Install Ackee

Install all required dependencies.

```sh
npm install
```

### 4. Run Ackee

Ackee will output the URL it's listening on once the server is running. Visit the URL with your browser and complete the final steps using the interface.

```sh
npm run start
```

### 5. Get Ackee online

Ackee now runs on port `3000` and is only accessible from your local network. It's recommended to use a reverse proxy in front of Ackee. The following guides will help you through these steps.

- [SSL and HTTPS](SSL%20and%20HTTPS.md)
- [CORS headers](CORS%20headers.md)

## With Netlify

### 1. Deploy to Netlify

[](https://app.netlify.com/start/deploy?repository=https://github.com/electerious/Ackee)

### 2. Configure Ackee

- You need to have a MongoDB instance running (e.g. [MongoDB Atlas](https://www.mongodb.com/cloud/atlas))
- Set a username and password to protect your interface
- Ensure that you're using the correct CORS headers by setting [`ACKEE_ALLOW_ORIGIN`](CORS%20headers.md#platforms-as-a-service-configuration).

### 3. Updating Ackee

Netlify adds a forked version of Ackee to your GitHub repositories. You can always [pull the newest code](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/syncing-a-fork) from the original project. Pushing those changes to your repository will automatically deploy the new version of Ackee on Netlify.

## With Vercel

### 1. Deploy to Vercel

[](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Felecterious%2FAckee&env=ACKEE_USERNAME,ACKEE_PASSWORD,ACKEE_MONGODB,ACKEE_ALLOW_ORIGIN&envDescription=Environment%20variables%20needed%20to%20configure%20the%20user%20and%20database%20connection&envLink=https%3A%2F%2Fgithub.com%2Felecterious%2FAckee%2Fblob%2Fmaster%2Fdocs%2FOptions.md)

When prompted to select a directory, select the root directory.

### 2. Configure Ackee

- Set the build command: `npm run build`
- Set the output directory: `dist`
- Set environment variables `ACKEE_USERNAME`, `ACKEE_PASSWORD`, `ACKEE_MONGODB`, and `ACKEE_ALLOW_ORIGIN`.

### 3. Updating Ackee

Vercel adds a forked version of Ackee to your GitHub repositories. You can always [pull the newest code](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/syncing-a-fork) from the original project. Pushing those changes to your repository will automatically deploy the new version of Ackee on Vercel.

## With Heroku

### 1. Deploy to Heroku

Deploy to Heroku by clicking this button:

[](https://heroku.com/deploy?template=https://github.com/electerious/Ackee)

### 2. Configure Ackee

- You need to have a MongoDB instance running, either hosting it yourself, using [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) or by using a (paid) add-on like [ObjectRocket MongoDB](https://elements.heroku.com/addons/ormongo). This is as simple as typing `heroku addons:create ormongo:2-wt --app <YOUR_APP_NAME>` using the CLI, or using the web dashboard; more details at the [official documentation](https://devcenter.heroku.com/articles/managing-add-ons). You'll need to provide connection details to Ackee dyno, either from the web dashboard or via command line, e.g. `heroku config:add "ACKEE_MONGODB=mongodb://<host>:<port>/<db>"`
- Ensure that you're using the correct CORS headers by setting [`ACKEE_ALLOW_ORIGIN`](CORS%20headers.md#platforms-as-a-service-configuration).

### 3. Updating Ackee

The easiest way to update Ackee once hosted on Heroku is to clone the repo down, pull the latest changes from Ackee, and then push them back up to Heroku. You'll need the Heroku CLI and Git for this to work.

In your application view, you'll find instructions under the `Deploy` tab on how to clone down the project. It should look something like below:

```sh
heroku login
heroku git:clone -a <your ackee application name>
```

You'll then want to add the Ackee repo as origin, pull the latest changes, and push it back up to Heroku.

```sh
git remote add origin https://github.com/electerious/Ackee.git
git pull origin master
git push heroku master
```

After your application re-deploys you'll have the latest version of Ackee!

## With Qovery

[Qovery](https://qovery.com) is a fully-managed cloud platform that runs on your AWS, GCP, Azure and Digital Ocean account where you can host static sites, backend APIs, databases, cron jobs, and all your other apps in one place.

### 1. Create a Qovery account.

Visit the [Qovery dashboard](https://console.qovery.com) to create an account if you don't already have one.

### 2. Create a project

Click on "Create a new project" and give a name to your project.

### 3. Add an application

Click on "Create an application" then choose "I have an application" and select your GitHub or GitLab repository where your Ackee app is located.

Select "MongoDB" service then choose a version. Give the database a name and continue with "Next".

Click on "Deploy".

Chat with Qovery developers on [Discord](https://discord.qovery.com) if you need help.

## With Render

You can use [Render's](https://render.com/) one-click deploy button to get up and running with Ackee in minutes.

Click **Deploy to Render** below and follow the prompts to set up Ackee on Render.

[](https://render.com/deploy?repo=https://github.com/render-examples/ackee)

Once your deploy has finished, you are ready to start using Ackee! Visit the URL for your service to login. You can get your login credentials from the `ACKEE_USERNAME` and `ACKEE_PASSWORD` environment variables in the **Environment** tab of your service. By default, your username will be `render` and your password will be a randomly generated string.

## With Railway

You can use the button below for a one-click deployment and have Ackee running within minutes. Make sure you are logged into your [Railway account](https://railway.app/), then follow the steps below.

### 1. Launch the Template

Launch the Ackee template on Railway by clicking this button:
[](https://railway.app/new/template/ackee)

### 2. Configure and Deploy

Upon launching the template, you will be prompted to configure the Ackee and MongoDB services.

- Click `Configure` on the Ackee service and enter your desired username in the `ACKEE_USERNAME` environment variable input. Click `Save Config`.
  - Note: A password will be automatically generated for you which you can retrieve from within the Ackee service's [variables tab](https://docs.railway.app/guides/variables#service-variables) after deployment.
- Click `Configure` on the MongoDB service. You do not need to set any variable configurations, simply click `Save Config`.
- Click `Deploy`

Upon deployment, everything should just work on its own. Railway will automatically provision the MongoDB database for you and link it to your Ackee service!

## With Koyeb

[Koyeb](https://www.koyeb.com) is a developer-friendly serverless platform to deploy apps globally. The platform lets you seamlessly run Docker containers, web apps, and APIs with git-based deployment, native autoscaling, free SSL, a global edge network, and built-in service mesh and discovery.

### 1. Configure Ackee

- You need to have a MongoDB instance running (e.g. [MongoDB Atlas](https://www.mongodb.com/cloud/atlas))
- Ensure that you're using the correct CORS headers by setting [`ACKEE_ALLOW_ORIGIN`](CORS%20headers.md#platforms-as-a-service-configuration)
- Have the [Koyeb CLI](https://www.koyeb.com/docs/cli/installation) installed which is the fastest way to deploy Ackee

### 2. Deploy Ackee

Before deploying Ackee on Koyeb, create three secrets to securely store your Ackee username, password, and your MongoDB URL.
In your terminal execute the following to create the secrets:

```sh
$ koyeb secret create mongodb-url
✔ Enter your secret: mongodb://<username>:<password>@<host>:<port>/<db>

$ koyeb secret create ackee-username
✔ Enter your secret: <ackee_username>

$ koyeb secret create ackee-password
✔ Enter your secret: <ackee_password>
```

Once you’ve created the secrets, you can deploy Ackee. In your terminal run the following command to create a new Koyeb App and deploy the Ackee service.

```sh
koyeb app init ackee --docker electerious/ackee --ports 3000:http --routes /:3000 --env ACKEE_USERNAME=@ackee-username --env ACKEE_PASSWORD=@ackee-password --env ACKEE_MONGODB=@mongodb-url --env ACKEE_ALLOW_ORIGIN="https://example.com"
```

Your Ackee service is being deployed. To retrieve the Ackee URL run:

```
$ koyeb app get ackee
ID                                  	NAME     	DOMAINS                         	UPDATED AT
30de8301-05b1-4131-a842-28e608900000	ackee   	ackee-<YOUR_KOYEB_ORG>.koyeb.app	2021-07-06 11:58:01.143967 +0000 UTC
```

Open the URL to access Ackee.

## With Zeabur

You can use the [Zeabur](https://zeabur.com/) button for a one-click deployment and have Ackee running within minutes.

[](https://zeabur.com/templates/6YH1QJ)

Upon clicking the button, you will be asked to set the `ACKEE_USERNAME` environment variables. Once you do that, everything should just work on its own.
Zeabur will automatically provision the MongoDB database for you and also link it to your Ackee deployment!

---

### Keyboard Shortcuts (`docs/Keyboard shortcuts.md`)

# Keyboard shortcuts

## Switch between views

In Ackee, you can quickly switch between views using the following keyboard shortcuts.

| Key     | View      |
| :------ | :-------- |
| `o`     | Overview  |
| `0`…`9` | Domain    |
| `v`     | Views     |
| `p`     | Pages     |
| `r`     | Referrers |
| `d`     | Durations |
| `e`     | Events    |
| `s`     | Settings  |

## Interact with modals

The following keys are supported when a modal is focused.

| Key     | Action        |
| :------ | :------------ |
| `enter` | Confirm modal |
| `esc`   | Close modal   |

---

### Options (`docs/Options.md`)

# Options

The following environment variables are used by Ackee. You can also create a `.env` file in the root of the project to store all variables in one file. Ackee uses Node.js native [--env-file](https://nodejs.org/en/learn/command-line/how-to-read-environment-variables-from-nodejs) support.

- [Database](#database)
- [Port](#port)
- [Username and password](#username-and-password)
- [TTL](#ttl)
- [Tracker](#tracker)
- [Environment](#environment)
- [Demo mode](#demo-mode)
- [CORS headers](#cors-headers)

## Database

MongoDB connection URI. See the [MongoDB connection string spec](https://docs.mongodb.com/manual/reference/connection-string/) for more detail.

```
ACKEE_MONGODB=mongodb://localhost:27017/ackee
```

_or_

```
MONGODB_URI=mongodb://localhost:27017/ackee
```

## Port

The port Ackee should listen on. Defaults to `3000`.

```
ACKEE_PORT=3000
```

_or_

```
PORT=3000
```

## Username and password

Username and password. Both are required to generate a new token.

```
ACKEE_USERNAME=username
ACKEE_PASSWORD=password
```

## TTL

Specifies how long a generated token is valid. Defaults to `86400000` (1 day).

```
ACKEE_TTL=86400000
```

## Tracker

Pick a custom name for the tracking script of Ackee to avoid getting blocked by browser extensions. The default script will always be available via `/tracker.js`. Your custom script will be available via `/custom%20name.js`. Ackee will encode your custom name to a URL encoded format. Avoid characters that can't be used in filenames.

Make sure to adjust the tracking script URL on your sites when changing this option. Sites that are using the default URL won't be affected.

```
ACKEE_TRACKER=custom name
```

## Environment

Set the environment to `development` to see additional error details and enable Apollo Sandbox. UI assets rebuilt with `npm run build:pre` will be unminified in this mode.

```
NODE_ENV=development
```

## Demo mode

Set to `true` to enable demo mode. In demo mode, mutations that change domains, events, or permanent tokens are blocked, and Apollo Sandbox is enabled. Visitor tracking and login/logout remain available.

```
ACKEE_DEMO=true
```

## CORS headers

Quick solution for setting [CORS headers](CORS%20headers.md) instead of using a [reverse proxy](SSL%20and%20HTTPS.md). This is helpful if you are running Ackee on a platform that handles SSL for you.

```
ACKEE_ALLOW_ORIGIN=https://example.com
```

_or_

```
ACKEE_ALLOW_ORIGIN=https://example.com,https://one.example.com,https://two.example.com
```

Setting a wildcard (`*`) is also supported, but not recommended. It's neither a secure solution nor does it allow Ackee to ignore your own visits. Please disable the `ignoreOwnVisits` option in ackee-tracker if using a wildcard is the only option for you.

```
ACKEE_ALLOW_ORIGIN=*
```

As opposed to manually configuring CORS domains, you can also automatically add CORS Headers for domains in the domain list that have [fully qualified domain names](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) as titles. To achieve this, set:

```
ACKEE_AUTO_ORIGIN=true
```

---

### Privacy Policy (`docs/Privacy Policy.md`)

# Privacy Policy

Ackee is a privacy first product that keeps tracked data anonymized. It's therefore not required to mention Ackee in your privacy policy. Ackee doesn't store IP addresses or other personal identifiable information by default. It doesn't even use cookies, therefore, it doesn't need to be added to your cookie banner either.

However, we recommend to mention Ackee in your privacy policy. Let your visitors know that you're using a privacy friendly analytics tool which doesn't collect personal identifiable information.

> ⚠️ Make sure to ask your users before activating the `detailed` mode of Ackee. The detailed mode tracks more data and also includes data [we're considering as personal data](Anonymization.md#personal-data). In this case it's highly recommended to add Ackee to your privacy policy.

## HTML privacy policy example

```html
To get information about the behavior of our visitors, we use
<a href="https://ackee.electerious.com" target="_blank" rel="noopener noreferrer">Ackee</a>. This analytics software
gives us insight about our visitors only in general, but not about individuals per se, as it does not track visitors and
does not store any personal identifiable information.
<a href="https://docs.ackee.electerious.com/#/docs/Anonymization" target="_blank" rel="noopener noreferrer"
  >Go to their documentation</a
>
to find out what Ackee collects.
```

## Markdown privacy policy example

```md
To get information about the behavior of our visitors, we use
[Ackee](https://ackee.electerious.com). This
analytics software gives us insight about our visitors only in general, but not
about individuals per se, as it does not track visitors and does not store any
personal identifiable information.
[Go
to their documentation](https://docs.ackee.electerious.com/#/docs/Anonymization) to find out what Ackee collects.
```

---

### SSL And HTTPS (`docs/SSL and HTTPS.md`)

# SSL and HTTPS

Ackee runs a simple server that doesn't support TLS/SSL. This means it's not possible to directly connect via HTTPS. It's recommended to use a reverse proxy instead. This document explains how.

## What is a reverse proxy?

> A reverse proxy is a type of proxy server that retrieves resources on behalf of a client from one or more servers. These resources are then returned to the client, appearing as if they originated from the proxy server itself.

A reverse proxy makes it easy for you to run Ackee on your server along with other services. It also allows you to secure connections using TLS/SSL.

I highly recommend [this article](https://medium.com/intrinsic/why-should-i-use-a-reverse-proxy-if-node-js-is-production-ready-5a079408b2ca) if you want to learn more about reverse proxies.

## Example configurations

### nginx

- [Recommended configuration](#recommended-configuration)
- [Single domain configuration](#single-domain-configuration)
- [Insecure wildcard configuration](#insecure-wildcard-configuration)

#### Recommended configuration

This configuration redirects all requests to the non-www domain `example.com`, secures connections using TLS/SSL and allows CORS requests from a list of known domains.

> 👉 The CORS headers are required so your sites can send data to Ackee, even when their domain is different to the one Ackee uses.

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Single domain configuration

This configuration secures all connections using TLS/SSL and allows `https://example.com` to send data to `https://ackee.example.com`.

> ℹ️ This configuration only allows requests from a single domain. Take a look at our [recommended configuration](#recommended-configuration) if you want to allow requests from multiple domains or use the [insecure wildcard configuration](#insecure-wildcard-configuration).

```conf
server {
    listen 443 ssl http2;

    server_name ackee.example.com;

    ssl_certificate     /etc/letsencrypt/live/ackee.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ackee.example.com/privkey.pem;

    access_log /var/log/nginx/log/ackee.example.com.access.log main;
    error_log  /var/log/nginx/log/ackee.example.com.error.log;

    location / {
        add_header          Access-Control-Allow-Origin "https://example.com" always;
        add_header          Access-Control-Allow-Methods "GET, POST, PATCH, OPTIONS" always;
        add_header          Access-Control-Allow-Headers "Content-Type, Authorization, Time-Zone" always;
        add_header          Access-Control-Allow-Credentials "true" always;
        add_header          Access-Control-Max-Age "3600" always;
        add_header          Strict-Transport-Security "max-age=31536000" always;
        add_header          X-Frame-Options deny;
        proxy_pass          http://localhost:3000;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect      off;
        proxy_buffering     off;
        proxy_set_header    Host $host;
        proxy_set_header    X-Real-IP $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```

#### Insecure wildcard configuration

A wildcard (`*`) isn't recommended as it's neither a secure solution nor does it allow Ackee to ignore your own visits. Please disable the `ignoreOwnVisits` option in ackee-tracker if using a wildcard is the only option for you.

```conf
server {
    listen 443 ssl http2;

    server_name ackee.example.com;

    ssl_certificate     /etc/letsencrypt/live/ackee.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ackee.example.com/privkey.pem;

    access_log /var/log/nginx/log/ackee.example.com.access.log main;
    error_log  /var/log/nginx/log/ackee.example.com.error.log;

    location / {
        add_header          Access-Control-Allow-Origin "*" always;
        add_header          Access-Control-Allow-Methods "GET, POST, PATCH, OPTIONS" always;
        add_header          Access-Control-Allow-Headers "Content-Type, Authorization, Time-Zone" always;
        add_header          Access-Control-Max-Age "3600" always;
        add_header          Strict-Transport-Security "max-age=31536000" always;
        add_header          X-Frame-Options deny;
        proxy_pass          http://localhost:3000;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect      off;
        proxy_buffering     off;
        proxy_set_header    Host $host;
        proxy_set_header    X-Real-IP $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```

---

### Systemd (`docs/Systemd.md`)

# Systemd

This guide shows you how to configure Ackee with systemd. We assume that your Ackee is located and installed in `/opt/Ackee`.

## 1. Make Ackee executable

Make the `index.js` executable with `chmod +x /opt/Ackee/src/index.js`.

## 2. Create the service

Create a file named `ackee.service` in `/etc/systemd/system/`.

```
[Unit]
Description=Ackee

[Service]
ExecStart=/opt/Ackee/src/index.js
Restart=always
User=nobody
Group=nogroup
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
WorkingDirectory=/opt/Ackee

[Install]
WantedBy=multi-user.target
```

## 3. Start the service

Start the service with `systemctl start ackee.service` and check if everything is up and running with `systemctl status ackee.service`.

## 4. Enable the service

Enable the service on boot with `systemctl enable ackee.service`.

---

### Upgrade Guide (`docs/Upgrade guide.md`)

# Upgrade guide

## Upgrading from version 2 to 3

### `Access-Control-Allow-Origin: "*"` not recommended

> This change is relevant for you when using a wildcard as the Access-Control-Allow-Origin.

Using a wildcard (`*`) for the `Access-Control-Allow-Origin` header was never recommended as it's neither a secure solution nor does it allow Ackee to ignore your own visits. Please disable the `ignoreOwnVisits` option in ackee-tracker if you're currently using a wildcard. The [SSL and HTTPS](SSL%20and%20HTTPS.md) guide contains better alternatives.

`ignoreOwnVisits` is now enabled by default and won't work when using a wildcard.

### New `Access-Control-Allow-Credentials` header

> This change is relevant for everyone.

Ackee requires [a new `Access-Control-Allow-Credentials` header](CORS%20headers.md#credentials) which was previously optional. Make sure to add this header in your server or reverse proxy configuration.

### ackee-tracker with new `.create` and `.record` syntax

> This change is only relevant for you when using ackee-tracker in the [Manually](https://github.com/electerious/ackee-tracker/blob/master/README.md#manually) or [Programmatic](https://github.com/electerious/ackee-tracker/blob/master/README.md#programmatic) way.

The [changelog of ackee-tracker](https://github.com/electerious/ackee-tracker/blob/master/CHANGELOG.md) contains everything you need to know when updating to the newest version.

### Referrers require `ReferrerType` in GraphQL API

> This change is relevant for you when using the GraphQL API.

A new parameter is required when requesting referrers via the GraphQL API. The parameter is called `ReferrerType` and can be `WITH_SOURCE`, `NO_SOURCE` or `ONLY_SOURCE`.

### Referrers can return non URL ids via GraphQL API

> This change is relevant for you when using the GraphQL API.

The `id` of requested referrers was always a URL, but has been changed to a string. That's because [referrers can now include parameters](Enhancing%20referrers.md) (e.g. `source` when using `ackee-tracker`).

---

--- METRICS ---
- Files Extracted: 15
- Estimated Token Budget: ~14605 tokens
- Canonical Reference: https://codewiki.google/github.com/electerious/Ackee
