Skip to content
Documentation

Administration guide

Hints, tips and best practices for the administration of ProjectForge

Written by
Table of Contents

1. Introduction

This document describes the administration of ProjectForge®: configuration, security, user directories, backups and maintenance. For installing ProjectForge (Docker or JAR, setup wizard, reverse proxy) please refer to Installation first.

All settings mentioned here go into projectforge.properties in ProjectForge’s home directory (written by the setup wizard), unless stated otherwise. The defaults with comments are listed in application.properties. Restart ProjectForge after changing this file.

2. ProjectForge Setup

2.1. Database

The setup wizard writes the database settings, so you usually don’t have to touch them. ProjectForge uses the connection pool of Spring Boot (HikariCP); the spring.datasource.hikari.* defaults are listed in application.properties.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Embedded HSQLDB (default, for tests and small installations):
spring.datasource.url=jdbc:hsqldb:file:${projectforge.base.dir}/database/projectforge;shutdown=true
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
spring.datasource.username=sa
spring.datasource.password=

# PostgreSQL (recommended for productive use):
#spring.datasource.url=jdbc:postgresql://localhost:5432/projectforge
#spring.datasource.driver-class-name=org.postgresql.Driver
#spring.datasource.username=projectforge
#spring.datasource.password=<your-password>

The schema is created and migrated automatically by Flyway on every start.

2.2. Initialization, first login and set-up dialogue

After the first start, ProjectForge® shows its setup page. Choose the target (empty database for a productive system, or a test system with example data) and set the administrator’s user name and password. All other settings such as time zone and e-mail addresses can be changed later under Administration → Configuration. See Installation – The setup page for details.

2.3. SSL / Https

It’s recommended to run ProjectForge on a localhost http port and to handle https with a reverse proxy such as NGINX, see Installation – Secure http connection.

For testing purposes you can use the following configuration:

2.3.1. Self-signed certificate

Generate a self-signed certificate: keytool -genkeypair -alias selfsigned -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 365

2.3.2. Configure the file ~/ProjectForge/projectforge.properties

1
2
3
4
5
6
# Bind to all interfaces, or localhost
server.address=0.0.0.0
server.port=8443
server.ssl.key-store=file:/home/projectforge/keystore.p12
server.ssl.key-store-password=<password of your certificate>
server.ssl.key-store-type=PKCS12

2.3.3. Trust the self-signed certificate

  1. Extract: keytool -exportcert -alias selfsigned -keystore keystore.p12 -storetype PKCS12 -file selfsigned.crt -rfc

  2. Import it into the certificate store of your system or browser and trust it.

2.3.4. Behind a reverse proxy: secure cookie, HSTS and CSP

If the reverse proxy terminates https, ProjectForge itself only sees http requests. Then the session cookie lacks the Secure flag and no Strict-Transport-Security header (HSTS) is sent. Tell ProjectForge about the original protocol in ~/ProjectForge/projectforge.properties:

# The proxy must send X-Forwarded-Proto (see doc/misc/nginx_sites-available_projectforge):
server.forward-headers-strategy=native
# Or, if every access is via https anyway:
#server.servlet.session.cookie.secure=true

ProjectForge sends its security headers itself (Content Security Policy, X-Frame-Options, Referrer-Policy, Permissions-Policy etc.). Don’t add them again in the proxy: a header sent twice with different values is ignored by the browser. The Content Security Policy can be replaced by an own value of projectforge.security.csp-header-value (an empty value sends none, which isn’t recommended).

2.4. PostgreSQL

2.4.1. Setting up the database

PostgreSQL 13 or newer is required: a database migration (Flyway V8.0.24) runs CREATE EXTENSION IF NOT EXISTS pg_trgm for the trigram indexes of the history search. pg_trgm is a trusted extension since PostgreSQL 13, so it’s sufficient that the database user of ProjectForge has the CREATE privilege on the database (e.g. as its owner, see below). Otherwise a database administrator has to create the extension once beforehand: psql -U postgres -d projectforge -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;".

The files of the file store are stored in an own schema pf_files, which is created by Flyway as well (so the database user also needs the right to create schemas, which the owner of the database has), see Backup.

  1. Create the user (e.g. as admin user): createuser -U postgres -P projectforge

  2. Create the database: createdb -U postgres -E UTF-8 -O projectforge projectforge

  3. You can drop an existing user and database with: dropdb projectforge and dropuser projectforge

  4. To re-use an existing database, restore a dump, see Restoring a backup.

Recommended settings in postgresql.conf (max_connections must be larger than ProjectForge’s connection pool, spring.datasource.hikari.maximum-pool-size, default 100). If the JCR still uses PostgreSQL as well (oak.datasource.url given), its own pool adds up to 10 more connections (oak.datasource.hikari.maximum-pool-size):

max_connections = 120
statement_timeout =  300000 # 300s
lock_timeout = 300000       # 300s

3. Configuration

3.1. Base directory (application’s home)

ProjectForge® needs a home directory, by default ${user.home}/ProjectForge (/ProjectForge inside Docker). It’s chosen by the setup wizard, see Installation for how ProjectForge finds it. The following files and directories are created if they don’t exist:

projectforge.properties

Configuration file, written by the setup wizard. A former config.xml is taken over into the configuration parameters on the first start and renamed to config.xml.migrated (may be removed afterwards).

database

The embedded database (HSQLDB), if used.

hibernateSearch

The search indices (Lucene). If corrupted, stop ProjectForge, delete the content of this directory and rebuild the indices via Administration → System → Reindex.

logs

The log files, e.g. ProjectForge.log.

resources

Your own resources, overriding the built-in ones: images (logos), fonts (for the PDF renderer), mail (e-mail templates), translations.

backup

Default directory for backups.

datatransfer

The files of the data transfer areas (plugin Data transfer).

jcr

The former document repository (JCR), only for installations not yet migrated, see Moving the files out of the JCR.

work

Working directory, e.g. for states of synchronizations.

tmp

Temporary files, e.g. debug logs.

The directories jcr, work, tmp and backup may be placed elsewhere by the properties projectforge.dir.jcr, projectforge.dir.work, projectforge.dir.tmp and projectforge.dir.backup (absolute or relative to the home directory).

3.2. Configuration file application.properties

ProjectForge® is a Spring Boot application, so you may use any of the common application properties in projectforge.properties. All ProjectForge-specific options with their defaults are listed in application.properties.

3.2.1. Properties editor

Administrators edit projectforge.properties on the page Administration → System (menu Configuration → Configuration file). The editor lists all parameters of application.properties, grouped by topic, with their descriptions and defaults. Parameters of your file that ProjectForge doesn’t know are marked: those in ProjectForge’s namespace (projectforge., pf.) are no longer used (or misspelled) and are listed in the group No longer used, where they can be removed at once; others (e.g. settings of Spring Boot) in the group Other. Passwords, keys and tokens are never shown: a secret can only be set to a new value.

  • Before every save, a copy of the file with a timestamp is placed next to it (projectforge.properties.<yyyy-MM-dd_HH-mm-ss>.bak). The file is written sorted by groups; your own comments stay above the parameter they were written above.

  • Changes take effect only after a restart of ProjectForge.

  • A value the running system uses differently (a restart is pending, or the value is set by an environment variable or the command line) is marked.

3.3. Switching off functions

Functions of ProjectForge can be switched off in projectforge.properties (a restart is needed). A switched off function has no menu entry, and its pages and REST services answer with 404, for everybody.

# Allowlist: all (default) or a comma separated list.
projectforge.features.enabled=all
# Denylist, wins over the allowlist.
projectforge.features.disabled=TIMESHEET_LIST,HR

There is one function per menu entry. Its id is the id of the menu entry (ADDRESS_LIST, TIMESHEET_LIST, FEEDBACK, MY_ACCOUNT, …​, see MenuItemDefId), of a plugin’s menu entry (toDo, DataTransferPersonalBox) or of a plugin (datatransfer, marketing). A main menu group (COMMON, FIBU, HR, MISC, …​) or a plugin stands for all of its entries. Plugins that aren’t activated are switched off as well, including their REST services. Unknown ids are logged as a warning on start-up.

This is no permission system, the access checks of the functions remain as they are. Some services stay reachable even with their function switched off, because other pages need them: the lookups of the input fields (e.g. of users or tasks), the subscription of calendars, and the core of the application (login, menu, settings, notifications, attachments of the remaining functions). The interfaces outside the pages and REST services aren’t switched off either: CardDAV (/carddav), the export of calendars (/export/ProjectForge.ics) and the SMS interface (/export/sms).

The menu visibility (projectforge.menu.visibility.*) is something else: it hides menu entries for groups of users only, the functions stay reachable.

3.4. Customization

3.4.1. Logo and title in pdf outputs

Under Administration → Configuration you can enter your organization name, which is used e.g. as headline of the pdf outputs.

Logos are configured in projectforge.properties. The files must be located in resources/images of the home directory (or be given as absolute paths):

1
2
3
4
5
6
# Logo of the web app:
projectforge.logoFile=logo.png
# Optional variant for the dark mode:
projectforge.logoFileDark=logo-dark.png
# Logo used in exports:
projectforge.export.logoFile=logo-export.png

3.4.2. Customizing i18n translations

ProjectForge allows you to customize or override any translation text displayed in the user interface. This is useful if you want to adapt terminology to your organization’s specific needs.

To customize translations:

  1. Create one or more properties files in the ~/ProjectForge/resources directory:

    • CustomerI18nResources.properties - for default/English translations

    • CustomerI18nResources_de.properties - for German translations

    • CustomerI18nResources_en.properties - for English translations (explicit)

  2. Add your custom key-value pairs to override existing translations or add new ones:

    1
    2
    3
    4
    
    # Example: Override existing translation
    menu.addressList=Contacts
    # Example: Add custom translation
    myorganization.custom.greeting=Welcome to Acme Corp
    
  3. Restart ProjectForge to apply the changes.

Your custom translations will take precedence over the default translations included in ProjectForge.

To find available i18n keys that you can override, refer to:

  • Main application translations: I18nResources.properties

  • Plugin translations: Search the GitHub repository for *I18nResources.properties files in the plugin directories, e.g.:

    • plugins/org.projectforge.plugins.banking/src/main/resources/BankingI18nResources.properties

    • plugins/org.projectforge.plugins.skillmatrix/src/main/resources/SkillMatrixI18nResources.properties

    • And other plugin-specific translation files

3.5. Configuration settings (web interface)

Under Administration → Configuration you can set further parameters of ProjectForge® such as the default time zone, the country phone prefix or cost-unit accounting. Each parameter is explained there. Structured settings are JSON values, e.g. the holidays (additional ones and changes of the built-in ones), the JIRA servers, the contract types and the accounting settings including the business assessment (see business assessment / BWA).

Administration → System offers among others: re-reading the configuration parameters, exporting the 2FA configuration, re-indexing the search index and system integrity checks.

3.6. Using ttf fonts for PDF render engine

Apache FOP is used as internal render engine for PDF outputs. Additional fonts are placed in resources/fonts of the home directory (or the directory configured by projectforge.fontsDirectory). Please refer to the FOP documentation for details on fonts.

3.7. HTML formatted E-Mail templates

ProjectForge works with Groovy templates for generating e-mails (e.g. notifications). You’ll find the built-in templates in the sources under projectforge-business/src/main/resources/mail. To modify a template, copy it to the same relative path in the resources directory of your home directory, e.g. ~/ProjectForge/resources/mail/orderChangeNotification.html, and edit it there. ProjectForge looks there first and falls back to the built-in template if the file doesn’t exist.

The templates are internationalized with the locale of the receiving user.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html>
#INCLUDE{mail/mailHead.html}
<body>
#INCLUDE{mail/mailOpening.html}
<br />
Here you can place content (don't forget to use i18n keys for supporting internationalized e-mails).
...
<br />
<%= pf.getString("link") %>: <a href="${requestUrl}">${requestUrl}</a>
<br />
This is an example of using dynamic data (here a list of positions will be printed):
<table>
  <% for (position in order.positions) { %>
    <tr>
      <th><%= pf.getString("title") %></th>
      <td>${position.title}</td>
      ...
    </tr>
  <% } %>
</table>

#INCLUDE{mail/mailHistoryTable.html}

#INCLUDE{mail/mailClosing.html}
#INCLUDE{mail/mailFooter.html}
</body>
</html>

3.8. Sending e-mails

ProjectForge sends notifications, password reset mails, 2FA codes and the error digest via SMTP:

1
2
3
4
5
6
7
8
9
mail.session.pfmailsession.emailEnabled=true
mail.session.pfmailsession.standardEmailSender=projectforge@acme.com
# Plain, StartTLS or SSL:
mail.session.pfmailsession.encryption=StartTLS
mail.session.pfmailsession.smtp.host=mail.acme.com
mail.session.pfmailsession.smtp.port=587
mail.session.pfmailsession.smtp.auth=true
mail.session.pfmailsession.smtp.user=projectforge
mail.session.pfmailsession.smtp.password=<password>

3.9. Support error digest

If a support address is configured (projectforge.support.mail) and sending mails is set up, ProjectForge collects the errors of a period and sends them to this address as one digest mail per interval. Nothing is sent for a period without errors. The digest collects:

  • unexpected errors in requests of logged-in users (no user mistakes such as validation or access errors),

  • logged errors (level ERROR),

  • external systems that aren’t reachable (LDAP, IdP, gateway, SMS/phone services, SMTP, …​), recognized by connection errors and timeouts, also from warnings,

  • sync runs that failed, were aborted or timed out since the last digest.

Equal errors (differing only in ids, numbers or quoted values such as a user name) are aggregated. If a new version groups them more coarsely, the problems recorded before are merged shortly after the start (counts summed, the admin’s status kept if they agree, else new). The mail lists new problems, regressions (problems that occurred again after being resolved) and spikes (much more often than in the last 7 days) first, then the 30 most frequent known problems sorted by severity (unreachable external systems first). Each problem links to the System dashboard (Administration), where it can be acknowledged, resolved, ignored or muted; ignored and muted problems are only counted. The attached text file contains up to five single occurrences per group with user, request and stack trace.

For an analysis or classification with an AI (or any other tool), the digest also attaches everything as a JSON file (format: projectforge-log-analysis), and the System dashboard downloads all problems of its current filter in the same format (Download as JSON), with sample message, stack trace and the occurrences of the last 30 days. The file explains its fields and values itself (guide), so it can be handed over as it is. It contains no user names, but messages and requests are as logged and may contain personal data: check this before passing it to an external service.

A new security or data problem (or one that occurred again after being resolved) doesn’t wait for the end of the interval: it sends the digest early, as soon as it reaches its threshold, but at most every 5 minutes.

Each problem goes to the recipients of its audience: developers (bugs, data problems), admins (external systems, configuration, sync runs) or security. Without recipients of its own, an audience gets the digest at projectforge.support.mail.

File: projectforge.properties
projectforge.support.mail=support@acme.com
# How often the digest is sent at most, e.g. 15m, 1h, 1d. 0 disables the digest. Default: 1h
projectforge.support.errorDigest.interval=1h
# Optional, comma separated, default: projectforge.support.mail
projectforge.support.errorDigest.recipients.developer=dev@acme.com
projectforge.support.errorDigest.recipients.admin=ops@acme.com
projectforge.support.errorDigest.recipients.security=security@acme.com

3.10. System dashboard and log viewer

Administrators find the System dashboard in the Administration menu. It shows the problems collected for the error digest (see above), the runs of the scheduled jobs and offers a log viewer for searching the log files, including the context of a log entry. The counts are stored in the database (projectforge.support.logAggregation. and projectforge.scheduler.stats. in application.properties, cleaned up by the daily privacy protection job).

3.11. Phone calls and texting with ProjectForge

3.11.1. Phone calls

If configured, users are able to initiate phone calls directly by clicking on a phone number of an address or by entering a name or a phone number on the direct call page. Calls are initiated via Sipgate:

1
2
3
4
5
6
projectforge.sipgate.baseUri=<base uri of the Sipgate REST API>
projectforge.sipgate.tokenId=<token id>
projectforge.sipgate.token=<token>
# Device used for calls, if the user hasn't configured own Sipgate devices (e.g. "p2"):
projectforge.sipgate.defaultDevice=
projectforge.sipgate.basePhoneNumber=
image

Figure 1:

You can initiate a phone call by simply clicking on a phone number of an address or by entering a name or number into the number field.

Don’t forget to configure your default country phone prefix under Administration → Configuration.

3.11.2. Texting

If an SMS gateway is configured, users are able to send text messages by clicking on the SMS symbol of an address or by entering a name or a cell phone number. ProjectForge calls your gateway by http; #number and #message are replaced by the user’s input. The text messages are also used as second factor (2FA), if configured.

1
2
3
4
5
6
7
projectforge.sms.url=https://smsgateway.acme.com/api.php
# GET (default) or POST:
projectforge.sms.httpMethod=GET
projectforge.sms.httpParameters={username:'projectforge',text:'#message',to:'#number'}
# Patterns of the gateway's response:
projectforge.sms.returnCodePattern.success=^OK.*
projectforge.sms.returnCodePattern.error=^Error.*
image

Figure 2:

You can send text messages by simply clicking on the SMS symbol of an address or by entering a name or cell phone number.

All options are listed in application.properties (projectforge.sms.*).

3.11.3. Phone lookup

With the phone lookup service, a telephony system (such as Asterisk) may look up the caller of incoming phone calls: projectforge.acme.com/rsPublic/phoneLookup?nr=<number>&key=<key>;. Wrong keys are delayed like failed logins.

The service is disabled as long as no key is configured (the default is empty). For enabling it, set your own long random secret key in projectforge.properties (e.g. generated with openssl rand -hex 32):

1
projectforge.phoneLookupKey=<your-random-secret>

3.12. Number ranges of customers, projects and cost units

Customers and projects have their own numbers, every project a system-wide unique project number. Number ranges (Nummernkreise) define which customer and project numbers belong together and how the cost units (Kost2) of a project are derived from its number. They are configured in projectforge.properties (projectforge.fibu.number-ranges.*). If you don’t use cost-unit accounting, or don’t need Kost2 of projects, no configuration is needed: customer and project numbers are free then.

3.12.1. Which ranges apply

On start, ProjectForge logs which ranges apply (Number ranges: mode …​):

Mode Applies if

CONFIGURED

At least one range is configured. Only the configured ranges apply.

LEGACY

No range is configured, but Kost2 of projects in the formerly built-in ranges exist: 4 for internal projects (4...) and 5 for customers 0-999 with up to 100 projects each (5...). These two ranges then apply as before. This is detected on every start and not stored.

FREE

Otherwise (e.g. a new installation). Customer and project numbers are free, projects get no Kost2. Kost2 without a project can still be created.

If an installation in mode LEGACY gets its first configured range, only the configured ranges apply. Then configure the former ranges 4 and 5 as well (see the example below).

3.12.2. Settings

Each range is a list entry projectforge.fibu.number-ranges.ranges[n].<property>:

Property Default Description

prefix

First digit (1-9) of the project and Kost2 numbers of this range, unique for all ranges.

name

Display name, e.g. Intern.

customer-min, customer-max

Lowest and highest customer number of this range; give both or none. A range without customers is for projects without customer (e.g. internal projects); its project-digits must be 5.

project-digits

2

Number of digits of the project number per customer (1-5), or of the whole range for a range without customers.

segments

1,3,2,2

Segments of the 8-digit Kost2 for display and input, e.g. 1,4,1,2 for 1.#... Must start with 1 and end with 2 (the Kost2 type) and sum up to 8.

Global settings:

Property Default Description

projectforge.fibu.number-ranges.allow-free-numbers

true

If false, every customer number must lie in a configured range (protects against typos) and a project needs a customer of a range or belongs to the range without customers; applies to the former ranges 4 and 5 as well.

projectforge.fibu.number-ranges.default-segments

1,3,2,2

Segments of the Kost2 whose first digit belongs to no range (Kost2 without project).

3.12.3. Project and Kost2 numbers

A project of a range gets a 6-digit project number, its Kost2 are this number followed by the 2-digit Kost2 type:

project number = prefix * 100000 + (customer mod 10^(5 - project-digits)) * 10^project-digits + project
project number = prefix * 100000 + project                      (range without customers)
Kost2          = project number * 100 + Kost2 type

The customer number itself is part of the project number: its last 5 - project-digits digits, whatever customer-min is. Example: range 1 with the customers 10000-19999 (or 10001-19999), 1 project digit and the segments 1,4,1,2. Project 3 of customer 10007 has the project number 100073 (shown as 1.0007.3), its Kost2 of type 02 is 1.0007.3.02. Customer 123 of range 5 (customers 0-999, 2 project digits) gets 5.123.xx.

So all customers of a range must share the digits in front of those last digits: a range with 2 project digits holds at most the 1000 customers of one block like 0-999 (100 projects per customer), a range with 1 project digit at most the 10000 customers of one block like 10000-19999.

File: projectforge.properties
# Former range 4: internal projects 4.###.##.##
projectforge.fibu.number-ranges.ranges[0].prefix=4
projectforge.fibu.number-ranges.ranges[0].name=Intern
projectforge.fibu.number-ranges.ranges[0].project-digits=5
# Former range 5: customers 0-999 with up to 100 projects each, 5.###.##.##
projectforge.fibu.number-ranges.ranges[1].prefix=5
projectforge.fibu.number-ranges.ranges[1].customer-min=0
projectforge.fibu.number-ranges.ranges[1].customer-max=999
projectforge.fibu.number-ranges.ranges[1].project-digits=2
# Customers 10000-19999 with up to 10 projects each, 1.####.#.##
projectforge.fibu.number-ranges.ranges[2].prefix=1
projectforge.fibu.number-ranges.ranges[2].customer-min=10000
projectforge.fibu.number-ranges.ranges[2].customer-max=19999
projectforge.fibu.number-ranges.ranges[2].project-digits=1
projectforge.fibu.number-ranges.ranges[2].segments=1,4,1,2
# Optional: refuse customer numbers outside these ranges
projectforge.fibu.number-ranges.allow-free-numbers=false

3.12.4. Notes

  • An inconsistent configuration (prefix used twice, overlapping customer numbers, too many customers for the project digits, invalid segments) stops the start with a message naming the range.

  • New customers and projects are offered the next free number of their range.

  • A project whose customer belongs to no range gets a free project number, which must not lie in the project numbers of a range (e.g. 100000-199999 for prefix 1). Such projects and projects in mode FREE can’t get Kost2.

  • Kost2 without a project must not start with the prefix of a range: the Kost2 of a range are those of its projects.

  • Stored project and Kost2 numbers are not renumbered if you change a range later. Existing Kost2 stay valid even if their range is no longer configured, but plan new ranges (customer numbers and project digits) before using them.

  • Invoicing quota (projectforge.invoicing-quota.): the patterns apply to the Kost2 numbers as their range segments them (e.g. 1. for 1.#..). With projectforge.invoicing-quota.billed-by-invoiced-kost2-types=true the time booked on Kost2 of customer projects with an invoiced Kost2 type counts as billed, independent of the digits.

3.13. Group nomenclature

The configuration parameter Group nomenclature (groupNomenclature, category finance) describes how groups are named. It is a description, not a rule: the project and structure wizards suggest the names of new groups following it, and its description is shown at the name of a group and in the wizards; no name is checked against it.

  • Description: rich text explaining the convention.

  • Pattern of the base name of a project’s groups, with the placeholders {customer.identifier}, {customer.name}, {customer.number}, {project.identifier}, {project.name} and {project.number}. An empty identifier is replaced by the name. Empty: the project’s name.

  • Suffixes of the manager, team and externals' groups, appended with a hyphen. Empty: the wizard’s default (pm, none, extern/external).

  • Letter case of the whole suggested name: unchanged, lower case or upper case.

Example: pattern {customer.identifier}-{project.identifier}, suffixes pm and extern, lower case suggest acme-portal-pm, acme-portal and acme-portal-extern. The structure wizard uses the pattern if the picked element belongs to a project, otherwise the element’s title.

3.14. business assessment / BWA

If you work with accounting records (DATEV) you may want to have business assessments (BWA in German). They are configured in the configuration parameter Accounting (fibu.accounting, element businessAssessment). The business assessments of DATEV consist of rows. Each row has a line number (no) and a title (for displaying). The accountRange defines which DATEV accounts match the row. Please note that a record is assigned to the first row found.
If the priority of a row is not high, the row will not be shown in the short form of business assessments. The value defines row amounts which are calculated: a formula of row ids starting with = or a Groovy script returning the amount. The account number ranges of debitors and creditors are part of this parameter, too. Example:

{
  "debitorsAccountNumberRanges": "10000-12999",
  "creditorsAccountNumberRanges": "70000-79999",
  "businessAssessment": {
    "heading": "BWA",
    "overallPerformance": "gesamtleistung",
    "merchandisePurchase": "matWareneinkauf",
    "preliminaryResult": "vorlaeufigesErgebnis",
    "rows": [
      {"no": "1020", "id": "umsatzErloese", "accountRange": "4000-4799", "priority": "MIDDLE", "title": "Umsatzerlöse", "indent": 1},
      {"no": "1051", "id": "gesamtleistung", "value": "=umsatzErloese", "priority": "HIGH", "title": "Gesamtleistung"},
      {"no": "1060", "id": "matWareneinkauf", "accountRange": "5700-5999", "priority": "HIGH", "title": "Mat./Wareneinkauf"},
      {"no": "1380", "id": "vorlaeufigesErgebnis", "value": "=gesamtleistung+matWareneinkauf", "priority": "HIGH", "title": "Vorläufiges Ergebnis"},
      {"id": "relativePerformance", "value": "if (gesamtleistung != 0.0) { return vorlaeufigesErgebnis / gesamtleistung } else { return 0.0 }", "priority": "HIGH", "title": "relative Performance", "scale": 4}
    ]
  }
}

3.15. SecurityConfig

3.15.1. Secure passwords

Passwords are never stored in clear text: every password is hashed together with a random salt per user and a pepper (see below). The minimum password length is configured under Administration → Configuration.

3.15.2. Protection against brute force attacks

If a login fails (via login page or REST service), a penalty time is set for the user name and for the client’s IP address. With every further failed login the penalty grows, and during the penalty no new attempt is accepted. The penalty for IP addresses grows much slower, so many users behind the same NAT aren’t locked out. Please refer to the source code (LoginProtection) for details.

The client’s IP address is taken from X-Forwarded-For only for requests coming from a trusted proxy (projectforge.security.trustedProxies, default: localhost and the private networks).

3.15.3. Salted passwords with pepper

A pepper string is configured in projectforge.properties; the setup wizard generates a random one. Before hashing a password, the pepper is concatenated to the salt and the password, so a database dump alone isn’t enough for a brute force attack.

1
projectforge.security.passwordPepper=*******SECRET********

Keep this value safe: if you lose or change the pepper, no user with a password hashed with the former pepper can log in any more. The same applies to projectforge.security.authenticationTokenEncryptionKey, which encrypts the users' authentication tokens (changing it only invalidates the tokens, not the passwords).

3.15.4. 2FA (two-factor-authentication)

2FA (two factor authentication) is supported by:

  • authenticator apps (TOTP, RFC 6238), e.g. Google or Microsoft Authenticator,

  • WebAuthn/FIDO2 (security keys such as YubiKey),

  • codes sent by e-mail or text message (SMS, if configured).

Each user sets up their second factors under My account.

You specify for which URLs a second factor is required and how long a successful 2FA is valid, using shortcuts for predefined sets of URI matchers and/or own regular expressions. A web filter checks for every request whether a 2FA is required and whether the last successful 2FA of the current session is older than the specified expiry period. The timestamp of a successful 2FA is stored in the user’s session as well as in the user’s stay-logged-in session.

Recommended example configuration:

File: projectforge.properties
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
projectforge.2fa.expiryPeriod.minutes1=
projectforge.2fa.expiryPeriod.minutes10=PASSWORD
projectforge.2fa.expiryPeriod.hours1=ADMIN_WRITE;MY_ACCOUNT
projectforge.2fa.expiryPeriod.hours8=ADMIN;SCRIPT_WRITE;FINANCE_WRITE;ORGA_WRITE;HR_WRITE
projectforge.2fa.expiryPeriod.days30=HR;FINANCE;ORGA;SCRIPT
projectforge.2fa.expiryPeriod.days90=
# Number of days a 2FA is required, when stay-logged-in is used. Without stay-logged-in and given, a 2FA is required
# after every login.
projectforge.2fa.loginExpiryDays=30
# Optional: no e-mail as second factor for members of these groups (comma separated):
projectforge.2fa.disableMail2FAForGroups=PF_Admin

Rules

  • An expression starting with / is an absolute path of the URI and is matched as a prefix: /rs/user → ^/rs/user.*, so any URI starting with /rs/user matches.

  • Empty entries mean: no second factor.

  • Regular expressions and shortcuts are separated by ;.

  • Supported shortcuts are ALL, ADMIN, ADMIN_WRITE, FINANCE, FINANCE_WRITE, HR, HR_WRITE, ORGA, ORGA_WRITE, SCRIPT, SCRIPT_WRITE, MY_ACCOUNT and PASSWORD, each containing a bunch of regular expressions.

  • You may also use WRITE:<entity-name> for specifying only the write access calls of an entity (REST calls). Reading of entities / detail view is not affected. For specifying all entity activities, use /rs/<entity-name> instead.

  • You may also define own expressions, such as: ADMIN;/rs/report;WRITE:user

  • If an URI matches several periods, the shortest one is used.

  • The former Wicket frontend (/wa/…​) is gone; its URLs are only redirected into the new frontend, so expressions with /wa/ don’t protect anything any more. Replace them by the /rs/…​ paths of the entities.

Please use Administration → System → Export 2FA configuration for verifying your configuration.

3.16. LDAP

3.16.1. Requirements

The LDAP must support the following additional schemas (example of slapd.conf of OpenLDAP):

1
2
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/inetorgperson.schema

3.16.2. Configuration

The groupBase setting is only needed in master mode. The managerUser is not needed in simple slave mode. For further information see below.

File: projectforge.properties
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
...
projectforge.ldap.server=
projectforge.ldap.baseDN=
projectforge.ldap.managerUser=
projectforge.ldap.managerPassword=
projectforge.ldap.port=
projectforge.ldap.sslCertificateFile=
projectforge.ldap.groupBase=
projectforge.ldap.userBase=
projectforge.ldap.authentication=
projectforge.ldap.posixAccountsDefaultGidNumber=
projectforge.ldap.sambaAccountsSIDPrefix=
projectforge.ldap.sambaAccountsPrimaryGroupSID=
...

3.16.3. Local (admin) users

Please log in as administrator with the stay-logged-in option of the login page (no LDAP configured yet). Restart ProjectForge with LDAP configured and use the same browser. You should be logged in. Edit every user who should be authenticated against ProjectForge (instead of LDAP), check the option local user and save. Now the local users may log-in without LDAP credentials. This is especially use-ful for administrator users of ProjectForge.

3.16.4. LDAP over SSL

openssl s_client -showcerts -connect ldap.acme.priv:636 > ldap.pem Copy the lines including -----BEGIN CERTIFICATE----- until -----END CERTIFICATE----- (including) to a file called ldap.cert. Now you can configure the certificate file in projectforge.properties or you can import the certificate to your java keystore. The first solution is independent of your JAVA version and is recommended first.

3.16.5. ProjectForge as LDAP master

The LDAP master mode is the most tested LDAP mode of ProjectForge and is used in production by us. In this mode, ProjectForge needs a LDAP user with write access to groups and users. The ProjectForge user’s will be synchronized in LDAP. If there is any conflict between LDAP and ProjectForge, the ProjectForge settings are used. The configuration of this mode is simple:

File: projectforge.properties
1
2
3
...
projectforge.login.handlerClass=LdapMasterLoginHandler
...
  1. This LDAP login handler has read-write access to the LDAP server and acts as master of the user and group data. All changes of ProjectForge’s users and groups will be written through.

  2. Any change of the LDAP server will be ignored and may be overwritten by ProjectForge.

  3. Passwords
    After each successful login at ProjectForge (via the login page) ProjectForges tries to authenticate the user with the given username/password credentials at LDAP. If the LDAP authentication fails ProjectForge changes the password with the actual password of the user (given on the login page). If the user changes their password (Change password), the LDAP password will be changed respectively. In LDAP master mode, the user is also able to change their "WLAN/Samba password" (Change WLAN/Samba password). The password entered there, will be written to the LDAP attribute "sambaNTPassword".

  4. Deactivated users
    Deactivated users will be moved to an sub userbase called "deactivated". The e-mail will be invalidated and the password will be deleted. Deleted and deactivated users are removed from any LDAP group. After reactivating the user, the password is set again when the user logs in the next time via the login page.

  5. Deleted Users
    Deleted users will not be synchronized and removed in LDAP if exist.

  6. New users
    New users (created in ProjectForge’s user edit page) will be created first without password in the LDAP system directly but with "WLAN/Samba password". Such users need to log-in first at ProjectForge, otherwise their LDAP passwords aren’t set (no log-in at any other system connecting to the LDAP is possible until the first log-in at ProjectForge).

  7. Modifying fields in LDAP
    ProjectForge does only modify supported fields of known LDAP users. If any modification is done in LDAP of fields and users/groups known by ProjectForge they will be overwritten by ProjectForge during the next synchronization run. If any modification in other fields of LDAP users are done, they should be left untouched by ProjectForge.

  8. Synchronization
    After any modification of any ProjectForge user or group, the synchronization is started for all LDAP users and groups. If no modification is done, the synchronization is done approximately hourly, but only if any user activity is detected in ProjectForge. You can force a synchronization by restarting ProjectForge.

  9. Groups and assigned users
    Users will be added as uniqueMember entries in the LDAP groups if also assigned in ProjectForge to the same ProjectForge group.

LDAP entries

See figures 3 and 4 for detailed information of how does ProjectForge stores and expect LDAP fields and object classes.

image

Figure 3:

ProjectForge uses the fields and object classes shown in this figure for users. The object class posixAccount with the fields uidNumber, gidNumber, homeDirectory and loginShell are only used and managed if configured). The field employeeNumber is used by ProjectForge for storing the primary key of the ProjectForge user entry in LDAP (for a better synchronization). So the renaming of user names should work.

image

Figure 4:

ProjectForge uses the fields and object classes shown in this figure for groups. The field businessCategory is used by ProjectForge for storing the primary key of the ProjectForge group entry in LDAP (for a better synchronization). So the renaming of group names should work.

Managing POSIX accounts

ProjectForge supports the management of POSIX accounts with the data uidNumber, gidNumber, homeDirectory, and loginShell.

You can administer the POSIX account data in the user edit page after configuring projectforge.properties like the above example. Please refer to the tool-tips for further information:

image

Figure 5:

ProjectForge is able to manage POSIX accounts in LDAP master mode.

  • After clicking the Button create beside the UID number field ProjectForge will prefill all POSIX account fields:

    1. The UID number will be the next free number (ProjectForge searches the highest used UID and increments it by one). If no such number found, 1,000 is assumed.

    2. The GID number will be preset with projectforge.ldap.posixAccountsDefaultGidNumber configured in projectforge.properties.

    3. The home directory will be the prefix /home/ followed by the user-name: e. g. /home/jdoe (not configurable).

    4. The login-shell will be preset with /bin/bash (not configurable).

      Don’t forget to save the user. After changing any of these POSIX account fields the LDAP entries are updated immediately.

  • ProjectForge presets a new UID number automatically (see above).

  • ProjectForge assures that no UID number is given twice.

  • Every LDAP entry gets the object-class posixAccount only if the POSIX account data were created in ProjectForge for this user.

3.16.6. Managing Samba accounts

File: projectforge.properties
1
2
3
4
...
projectforge.ldap.sambaAccountsSIDPrefix=
projectforge.ldap.sambaAccountsPrimaryGroupSID=
...
image

Figure 6:

ProjectForge is able to manage POSIX and Samba accounts in LDAP master mode.

  • After clicking the Button create beside the UID number field ProjectForge will prefill all SAMBA account fields:

    1. The UID number will be the next free number (ProjectForge searches the highest used UID and increments it by one). If no such number found, 1,000 is assumed.

    2. The GID number will be preset with the defaultSambaPrimaryGroupSID configured in projectforge.properties.

    3. Parallel usage with POSIX, which is recommended:
      Please click the 'create' button near to the UID of the POSIX account, the suffix of SAMBASID will be the same UID. ProjectForge’s tries first to get the same UID for SAMBA as for POSIX if no conflict was found.

      Don’t forget to save the user. After changing any of these Samba account fields the LDAP entries are updated immediately.

  • Every LDAP entry gets the object-class sambaSamAccount only if the Samba account data were created in ProjectForge for this user.

3.16.7. ProjectForge as LDAP client

This LDAP login handler acts as a LDAP slave, meaning, that LDAP will be accessed in read-only mode. There are 3 modes available: simple, users and users-groups. The configuration of this mode is simple:

File: projectforge.properties
1
2
3
...
projectforge.login.handlerClass=LdapSlaveLoginHandler
...
Simple mode

The simple mode is assumed if no ldap managerUser is given in the projectforge.properties.

  • Simple means that only username and password is checked, all other user settings such as assigned groups and user name etc. are managed by ProjectForge.

  • No ldap user is needed for accessing users or groups of LDAP, only the user’s login-name and password is checked by trying to authenticate!

  • If a user is deactivated in LDAP the user has the possibility to work with ProjectForge unlimited as long as he uses his stay-logged-in-method! (If not acceptable please use the normal user mode instead.)

  • For local users any LDAP setting is ignored.

Users mode

The normal user mode is assumed if a LDAP managerUser is given in the projectforge.properties.

  • Normal means that username and password is checked and all other user settings such as user name etc. are read by a given LDAP manager user.

  • If a user is deleted in LDAP the user will be marked as deleted also in ProjectForge’s data-base. Any login after synchronizing isn’t allowed (the stay-logged-in-feature fails also for deleted users).

  • For local users any LDAP setting is ignored.

  • All known LDAP user fields of the users are synchronized (given name, surname, e-mail etc.).

Users-groups mode

This mode is not yet implemented, the users-mode is used instead.

3.16.8. ProjectForge without LDAP

You can also use ProjectForge without LDAP (default).

File: projectforge.properties
1
2
3
...
projectforge.login.handlerClass=LoginDefaultHandler
...

3.17. OAuth2 / OIDC Authentication

ProjectForge supports authentication via an external OAuth2/OIDC provider. Users log in through the identity provider (e.g. Authentik, Keycloak) and are redirected back to ProjectForge. The user must already exist in ProjectForge (matched by idpExternalId or username).

3.17.1. Configuration

Add the following OAuth2/OIDC client configuration to projectforge.properties:

spring.security.oauth2.client.registration.authentik.client-id=${AUTHENTIK_CLIENT_ID}
spring.security.oauth2.client.registration.authentik.client-secret=${AUTHENTIK_CLIENT_SECRET}
spring.security.oauth2.client.registration.authentik.scope=openid,profile,email
spring.security.oauth2.client.registration.authentik.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.authentik.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.provider.authentik.issuer-uri=${AUTHENTIK_ISSUER_URI}

The registration id must be authentik, also for other providers such as Keycloak: ProjectForge activates its OAuth2 login only if spring.security.oauth2.client.registration.authentik.client-id is set. For Keycloak, just set the issuer URI of your realm (e.g. auth.acme.com/realms/acme.com) and the client of ProjectForge. The redirect URI to register at the provider is <your-projectforge-domain>/login/oauth2/code/authentik.

The login page of ProjectForge has no button for the identity provider: users start the login via <your-projectforge-domain>/oauth2/authorization/authentik (e.g. as a link or bookmark, or from the application dashboard of your identity provider).

3.17.2. User mapping

On successful OAuth2 login, ProjectForge resolves the local user account as follows:

  1. Match the OIDC sub claim against the idpExternalId field of ProjectForge users

  2. If no match is found, fall back to matching the preferred_username claim against the ProjectForge username

  3. If still no match: login is rejected

Deactivated users are always rejected regardless of a valid OAuth2 session.

3.17.3. External Gateway mode

ProjectForge can run as a reduced, public-facing instance that authenticates exclusively via OAuth2/OIDC. In this mode only the following services are exposed:

  • CardDAV (address book sync)

  • CalDAV / ICS subscriptions (calendar export)

  • DataTransfer (file exchange areas)

Activate with the Spring profile external-gateway:

--spring.profiles.active=external-gateway
Minimal gateway configuration

The gateway instance’s projectforge.properties requires the gateway mode and the database connection. All other gateway settings (CardDAV, the functions switched off, see Switching off functions) are provided by the external-gateway profile automatically. Without the profile, the gateway mode switches off all functions but the data transfer as well.

The OAuth2/OIDC configuration is only needed for the DataTransfer UI: CardDAV and CalDAV/ICS authenticate via tokens and work without it. If the OAuth2 client is not configured, the gateway starts without a login option.

ProjectForge adds the home projectforge.properties as --spring.config.additional-location, which Spring loads with the highest priority. It therefore overrides the external-gateway profile, so keep this file limited to the settings below rather than repeating profile defaults.
# Public URL of the gateway, including the port if published directly.
# This is the {baseUrl} of the OAuth2 redirect URI below.
projectforge.domain=https://gateway.example.com

# Gateway mode
projectforge.gateway.enabled=true
projectforge.gateway.sync.secret=<shared-secret>

# PostgreSQL (adjust host/credentials to your setup)
spring.datasource.url=jdbc:postgresql://postgres:5432/projectforge
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=projectforge
spring.datasource.password=<your-password>

# Required: the DAV and calendar tokens pushed by the main instance are stored encrypted.
projectforge.security.authenticationTokenEncryptionKey=<your-key>

# OAuth2/OIDC (only required for the DataTransfer UI)
spring.security.oauth2.client.registration.authentik.client-id=${AUTHENTIK_CLIENT_ID}
spring.security.oauth2.client.registration.authentik.client-secret=${AUTHENTIK_CLIENT_SECRET}
spring.security.oauth2.client.registration.authentik.scope=openid,profile,email
spring.security.oauth2.client.registration.authentik.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.authentik.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.provider.authentik.issuer-uri=${AUTHENTIK_ISSUER_URI}

For testing with HSQLDB (no external database needed), replace the PostgreSQL block:

spring.datasource.url=jdbc:hsqldb:file:${projectforge.base.dir}/database/projectforge;shutdown=true
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
spring.datasource.username=sa
spring.datasource.password=

The internal ProjectForge instance syncs user data to the gateway via the shared secret.

Main instance push configuration

The main (internal) ProjectForge instance must be configured to push user, group, address and calendar data to the gateway. Add the following to the main instance’s projectforge.properties:

projectforge.gateway.push.enabled=true
projectforge.gateway.push.url=https://gateway.example.com/api/gateway/sync
projectforge.gateway.push.secret=<shared-secret>
projectforge.gateway.push.syncIntervalMs=900000
Property Description

push.enabled

Enable periodic sync push to the gateway instance

push.url

Base URL of the gateway’s sync API endpoint

push.secret

Shared secret (must match projectforge.gateway.sync.secret on the gateway)

push.syncIntervalMs

Interval between sync pushes in milliseconds (default: 900000 = 15 minutes)

push.initialDelayMs

Delay of the first sync push after start-up in milliseconds (default: 120000 = 2 minutes)

push.publicUrl

URL of the gateway for browsers (external links of data transfer areas), only if it differs from push.url without /api/gateway/sync

The push service sends users, groups, address books, and cached ICS calendar data to the gateway. All requests are authenticated via the X-Gateway-Secret HTTP header.

Data transfer areas with external download or upload are administered on the main instance and pushed to the gateway with each sync. Their external link points to the gateway, external users and the internal users with access work with the files there; on the gateway these areas are read-only and keep the ids of the main instance. The gateway has only these areas, it creates none itself (no personal boxes). Switching off the external access or deleting the area on the main instance removes it from the gateway. The gateway sends the notification mails of these areas to their observers (e.g. on an external upload), so it needs a mail server configuration; for this the push includes the email addresses of these observers, and only theirs. With projectforge.datatransfer.externalAccess.localAllowed=true the external access runs on the main instance instead (nothing is pushed), with false it can’t be enabled at all.

3.18. Identity Provider Sync (IdP)

ProjectForge can act as a master for user and group data, provisioning them into an external identity provider. This is independent of the OAuth2 login described above — it handles user/group lifecycle management. Currently supported providers are Keycloak and Authentik.

3.18.1. Configuration

# Provider: "keycloak" (default) or "authentik"
projectforge.idp.provider=keycloak
projectforge.login.handlerClass=IdpMasterLoginHandler

# Optional: sync passwords to IdP on successful login
projectforge.idp.syncPasswords=false

3.18.2. Keycloak setup

Realm and client
  1. Create a realm (e.g. acme.com)

  2. Enable locales globally: de, en

  3. Create a client projectforge (confidential, service account enabled)

  4. Note down the client secret

projectforge.keycloak.serverUrl=https://auth.acme.com
projectforge.keycloak.realm=acme.com
projectforge.keycloak.clientId=projectforge
projectforge.keycloak.clientSecret=<secret>
projectforge.keycloak.pageSize=100
User attributes

Create the following custom user attributes in the Keycloak realm:

Attribute Min Length Max Length Notes

jiraUsername

0

100

mobilePhone

0

255

gender

Options: MALE, FEMALE, DIVERSE, UNKNOWN

locale

0

—

Validated automatically (de, en)

organization

0

255

description

0

255

nickname

0

255

sambaNTPassword

0

255

WLAN password (Samba NT hash)

Set the username validator minimum length to 2 (Keycloak default is 3; reduced for short usernames like jo).

Users must not be allowed to edit their own attributes (name, first name, etc.) in Keycloak.

3.18.3. Authentik setup

  1. Create a new role (e.g. projectforge-sync) with permissions: Group (add, change, delete, view), User (add, change, delete, view, add/remove user to group)

  2. Create a sync user in Admin with this role

  3. Create an API token: Admin → Directory → Tokens and App passwords → Create

projectforge.authentik.serverUrl=https://authentik.acme.com
projectforge.authentik.apiToken=<api-token>
projectforge.authentik.pageSize=100

Authentik stores custom attributes as a JSON object on users/groups — no pre-configuration of attribute schemas is needed.

3.18.4. Attribute mappings

Configure which ProjectForge fields are synced to IdP user/group attributes:

projectforge.idp.userAttributes.jiraUsername=jiraUsername
projectforge.idp.userAttributes.mobilePhone=mobilePhone
projectforge.idp.userAttributes.gender=gender
projectforge.idp.userAttributes.locale=locale
projectforge.idp.userAttributes.organization=organization
projectforge.idp.userAttributes.description=description
projectforge.idp.userAttributes.nickname=nickname

projectforge.idp.groupAttributes.description=description
projectforge.idp.groupAttributes.organization=organization

3.18.5. WLAN password sync (optional)

projectforge.idp.wlanPasswordAttribute=sambaNTPassword

3.18.6. Login handlers

Handler Description

LoginDefaultHandler

No external IdP (default)

IdpMasterLoginHandler

ProjectForge is master; users/groups are pushed PF → IdP

IdpLoginHandler

IdP is master; users/groups are pulled IdP → PF

3.19. Troubleshooting

3.19.1. NullPointerException on pages with diagrams under Linux

If you will receive exceptions while calling pages with diagrams such as under menu project management → my statistics:

Caused by: java.lang.NullPointerException
        at java.desktop/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1262)

the Linux package fontconfig probably isn’t installed. Please install this package and restart ProjectForge.

4. Maintenance

4.1. Updating

Updating is easy: back up your database, stop ProjectForge and start the new version (new JAR file or docker pull micromata/projectforge and re-create the container). Database schema and data are migrated automatically (Flyway) on the first start. Please check the Migration guide for the few changes that need your attention.

4.2. Re-indexing the database

The search index is rebuilt every night (see Scheduler). You may also start it manually under Administration → System → Reindex, e.g. after restoring a backup.

4.3. Analysing tools

The log files are located in logs of the home directory; administrators may also search them with the log viewer of the System dashboard. For analysing a hanging or slow server, the JDK tools are useful:

1
2
3
jps -l                 # Lists the Java processes and their pids.
jstack <pid>           # Thread dump, e.g. for finding blocked threads.
jcmd <pid> GC.heap_info

In Docker, run them inside the container (docker exec -it projectforge bash); the JRE image may not contain all JDK tools, then kill -3 <pid> writes a thread dump to the container’s output (docker logs projectforge).

4.4. Backup

All datasets of ProjectForge® are stored in the underlying database (e. g. PostgreSQL). You should install a Unix cron script making frequently a backup of the database (see Backup script).

4.4.1. Embedded database (HSQLDB)

With the embedded database, everything is located in ProjectForge’s home directory: the database (database), the configuration files, your resources, the data transfer files (datatransfer) and, if still used, the JCR (jcr). The files of the file store are part of the embedded database. For a backup, stop ProjectForge and copy the whole home directory (e.g. tar czf projectforge-home_2026-10-06.tar.gz -C $HOME ProjectForge); logs and hibernateSearch may be left out. For restoring, stop ProjectForge, replace the home directory by the copy and start ProjectForge again (rebuild the search index if you left out hibernateSearch). If the data transfer files are encrypted, the key mustn’t be part of this backup, see Encryption of the data transfer files.

4.4.2. PostgreSQL dumps

The files of the new file store (attachments of contracts, orders, invoices etc., see Moving the files out of the JCR) are stored in the own schema pf_files. This schema may become large, so two dumps are made:

  • The main dump without the files: pg_dump -N pf_files …​ (excludes the schema pf_files).

  • The dump of the files only: pg_dump -n pf_files -Fc …​ (custom format, restorable in parallel by pg_restore -j).

Both dumps are independent of each other: there are no foreign keys between pf_files and the other tables. Differences after restoring dumps of different days are reported by the sanity checks, which run nightly (projectforge.cron.sanityChecks) and on demand via Administration → System → Check system integrity.

Data transfer files (<home>/datatransfer) are not part of the dumps: their metadata is in pf_files, but the content is stored in the file system. The backup script archives them as projectforge_datatransfer_…​tar (uncompressed, the files are already stored compressed). If they are encrypted, the key isn’t part of this archive, see Encryption of the data transfer files.

4.5. Restoring a backup

4.5.1. Data base

1
2
gunzip projectforge_2026-10-06_02-00.sql.gz
psql -q -f projectforge_2026-10-06_02-00.sql -U postgres projectforge > log.txt 2>&1

Restoring the files afterwards (schema pf_files, the main dump doesn’t contain it):

1
pg_restore -U postgres -d projectforge -j 4 projectforge_files_2026-10-06_02-00.dump

Restoring the data transfer files (from the same backup as the files dump, because their metadata is in pf_files), with ProjectForge stopped and after removing the existing directory datatransfer:

1
tar xf projectforge_datatransfer_2026-10-06_02-00.tar -C ~/ProjectForge

Restore both dumps before starting ProjectForge: the main dump contains the Flyway history, so Flyway doesn’t create the schema pf_files again. If the schema already exists in the target data base, drop it first (DROP SCHEMA pf_files CASCADE;) or use pg_restore --clean --if-exists. After restoring, run the sanity checks via Administration → System → Check system integrity (they also report missing data transfer files).

For dropping and creating a new database, see PostgreSQL. Don’t forget to rebuild the search index afterwards (Administration → System → Reindex)!

Hint

If you want to use productive data in a test system, change the users' e-mail addresses (e.g. to your own) in the test database first, so nobody gets notifications from the test system. Please also mind data protection.

4.5.2. JCR repository

Only relevant for installations still using the JCR (projectforge.files.store=jcr), see Moving the files out of the JCR. The JCR repository contains the uploaded files (contracts, orders etc.). Its nightly backups are written to the backup directory.

For restoring a JCR backup, please refer the file backupReadme.txt inside the backup zip file.

If you have a corrupted JCR repository or if you want to make a full backup, refer backupReadme.txt as well. You will also find the readme here: github.com/micromata/projectforge/blob/develop/projectforge-jcr/src/main/resources/backupReadme.txt.

4.5.3. Moving the files out of the JCR

The JCR (Apache Oak) is going to be replaced by a new file store: the files are stored in the data base (schema pf_files), the data transfer files in the file system (<home>/datatransfer). The store is chosen in projectforge.properties:

# jcr: all files stay in the JCR (as before).
# db: all files are stored in the new file store.
projectforge.files.store=db

If not configured, db is used for new installations and jcr for installations with an existing JCR (unless the new file store already contains files of the data base storage, then db). So nothing changes for an existing installation until you switch to db.

After switching to db and restarting, ProjectForge copies all files of the JCR in the background. Every file is verified by size and SHA-256 checksum. The result is written to <home>/jcr-migration-report.txt; the migration is started on every start-up until a run finished without errors. It may also be started manually on the system page (card "Document repository (JCR)").

  • All files are copied, the JCR keeps them. So you may switch back to jcr or roll back to the former release; only files uploaded under db are missing after a rollback.

  • Data transfer files need disk space twice (JCR and <home>/datatransfer) until they expire. Deleted files are deleted in both stores.

  • If the data transfer files should be encrypted, configure the key before switching to db, see Encryption of the data transfer files. The copies in the JCR remain unencrypted until they expire or the JCR is removed.

  • The nightly JCR backup keeps running in both modes as long as the JCR exists. With db, the files are part of the files dump (schema pf_files, see Backup script), the data transfer files are archived by the backup script (<home>/datatransfer).

Before updating to a release without JCR, either switch to db and wait for a successful migration, or create a fresh JCR backup ZIP right before the update (system page, card "Document repository (JCR)", Create JCR backup ZIP): the next release imports the documents from this ZIP into the database. The ZIP doesn’t contain the data transfer files.

4.6. Automatical backup

4.6.1. Backup script

Install a crontab entry as user projectforge on your ProjectForge® server (or the database server) starting the following script:

File: pforgebackup.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash

PGSQL_DIR=/usr/bin
BACKUPDIR=/home/projectforge/backup
# Home directory of ProjectForge:
PF_HOME=/home/projectforge/ProjectForge
BASENAME=projectforge
TIMESTAMP=`date '+%Y-%m-%d_%H-%M'`

echo Starting backup of ProjectForge ...

# Main dump: all data except the files (schema pf_files).
BACKUPFILE=$BACKUPDIR/${BASENAME}_${TIMESTAMP}.sql
# Dump of the files only (schema pf_files, attachments of contracts, orders, invoices etc.).
FILES_BACKUPFILE=$BACKUPDIR/${BASENAME}_files_${TIMESTAMP}.dump
# Archive of the data transfer files (content in the file system, metadata in pf_files).
DATATRANSFER_BACKUPFILE=$BACKUPDIR/${BASENAME}_datatransfer_${TIMESTAMP}.tar

echo Using filenames $BACKUPFILE, $FILES_BACKUPFILE and $DATATRANSFER_BACKUPFILE ...

echo Exporting data ...
$PGSQL_DIR/pg_dump -U projectforge -N pf_files -f $BACKUPFILE || { echo "Error: export of data failed."; exit 1; }

echo Zipping backupfile ...
gzip $BACKUPFILE

echo Exporting files ...
# Custom format; -Z 0: the content is already compressed (or not compressible).
$PGSQL_DIR/pg_dump -U projectforge -n pf_files -Fc -Z 0 -f $FILES_BACKUPFILE || { echo "Error: export of files failed."; exit 1; }

if [ -d $PF_HOME/datatransfer ]; then
  echo Archiving data transfer files ...
  # Uncompressed: the files are already stored compressed (or encrypted). Exit code 1 of tar means that files changed
  # while reading (e.g. an upload during the backup), the archive is usable anyway.
  tar cf $DATATRANSFER_BACKUPFILE -C $PF_HOME datatransfer
  [ $? -le 1 ] || { echo "Error: archiving of data transfer files failed."; exit 1; }
fi

# For using scp for remote backup:
#scp $BACKUPFILE.gz $FILES_BACKUPFILE $DATATRANSFER_BACKUPFILE <user>@backup.mydomain:backup/

# Deleting of daily backups is done by ProjectForge (projectforge.cron.purgeBackupDir, see below). Monthly backups
# are kept forever, because they're needed for financial, yearly comparisons.

echo Backup of ProjectForge finished.

ProjectForge purges the daily backups (keeping weekly and monthly ones), if the backup dir is configured in projectforge.properties:

projectforge.cron.purgeBackupDir=/home/projectforge/backup
# Optional: comma-separated prefixes of the backup series. If not given, all files containing a date are processed.
# Each series (text in front of the date, here projectforge_, projectforge_files_ and projectforge_datatransfer_) is
# purged on its own.
#projectforge.cron.purgeBackupFilesPrefix=projectforge_

The files dump projectforge_files_…​ and the archive projectforge_datatransfer_…​ are series of their own, so they are purged like the main dump (also with the prefix projectforge_, because they start with it).

For allowing pg_dump without prompting a password, run the script as the Unix user projectforge and add the following line to pg_hba.conf (peer authentication: the Unix user name must match the database user name):

File: pg_hba.conf
1
local projectforge projectforge peer

Alternatively, store the password in ~/.pgpass of the user running the script (chmod 600 ~/.pgpass): localhost:5432:projectforge:projectforge:<password> (then add -h localhost to the pg_dump calls).

4.6.2. Adding crontab

Adding following entry to the Unix crontab:

1
2
# run every day at 2:00 am
0 2 * * * /home/projectforge/bin/pforgebackup.sh

4.6.3. Remote backup

For remote backup via scp you need to use key authorization:

  1. Generate keys (using empty password) ssh-keygen

  2. Copy your public key, e. g. identity.pub to your backup server.

  3. Add this key on the backup server to the authorized keys.

4.6.4. Polling remote backup

For security reasons it’s also possible to get the backup files from the ProjectForge® server with a cronjob starting e. g. one hour later:

File: pforgePollBackup.sh
#!/bin/bash
SERVER=projectforge.mydomain
DIR=/home/projectforge/backup
REMOTE_DIR=backup REMOTE_USER=projectforge
# Add e.g. -e "ssh -p <port>" if ssh doesn't use the default port.
rsync -avz -C $REMOTE_USER@$SERVER:$REMOTE_DIR $DIR

In that case you have to generate ssh key on your backup server and to add it to the authorized keys on your ProjectForge® server. Add the following entry to your crontab on the backup server:

1
2
# run every day at 3:00 am
0 3 * * * /home/projectforge/bin/pforgePollBackup.sh

4.7. Monitoring

You may monitor ProjectForge with your monitoring tool (such as Nagios) by calling the public system status, e.g. projectforge.acme.com/rsPublic/systemStatus. It answers with a small JSON object as soon as the web server is up. Without a login, version and build date are only placeholders (<version>, <date>), so only the application name (appname) and the start-up state are useful for monitoring: setupRedirectUrl is given as long as the setup isn’t completed.

Problems at runtime (errors, unreachable external systems, failed scheduler runs) are reported by the Support error digest and shown on the System dashboard.

4.8. Scheduler

4.8.1. Data base search index

The search index is rebuilt every night by the nightly job (projectforge.cron.nightly, default 2:30). ProjectForge runs with the time zone UTC (independent of the server’s time zone), so the cron expressions of projectforge.cron.* are UTC. Errors are logged and therefore part of the error digest. The runs of all scheduled jobs are listed on the System dashboard.

4.9. Uninstalling ProjectForge

Delete the JAR file (or the Docker container and image) and ProjectForge’s home directory. If you’re not sure where the home directory is located, look at the log output of the start-up: ProjectForge logs the home directory it uses. If you use PostgreSQL, drop the database as well (see PostgreSQL).

Hint

Please note: If you delete the home directory, all data of the embedded database is deleted as well!

4.10. Security improvements

4.10.1. Fail2ban

Failed logins are logged as warnings, including the client’s IP address (<user>@<ip> in front of the logger name):

…​ WARN @192.0.2.10 …​ LoginDefaultHandler : User login failed: jdoe/**

A starting point for a fail2ban filter (please verify it with fail2ban-regex against your log file logs/ProjectForge.log):

[Definition]
failregex = @<HOST>\s.*LoginDefaultHandler\s*: User login failed:

5. Plugins

Some features are built-in plugins which aren’t enabled automatically. They are activated by an administrator under Administration → Plugins (or via projectforge.plugins.ensure-active in projectforge.properties). Please restart ProjectForge after (de-)activating a plugin, see also Installation – Activation of built-in plugins.

5.1. Data transfer

Data transfer areas for exchanging (large) files between users, groups and also external users. Recommended for most installations. The files are stored in datatransfer of the home directory and expire automatically.

5.1.1. Encryption of the data transfer files

With the new file store (projectforge.files.store=db, see Moving the files out of the JCR), the data transfer files are stored compressed (unless already compressed by their format, e.g. zip, pdf, videos) and, if a key is configured, encrypted (AES-256). So copies of the home directory (e.g. backups) don’t contain them in plain text:

# A long random value, e.g. generated by: openssl rand -base64 32
projectforge.files.fileSystemEncryptionKey=...
  • Keep the key apart from the backups of the home directory, otherwise they contain the key too. Instead of projectforge.properties, the key may be given as environment variable PROJECTFORGE_FILES_FILESYSTEMENCRYPTIONKEY.

  • Keep a copy of the key in a safe place: if it’s lost or changed, the encrypted files can’t be read anymore (the sanity check reports them). A missing or changed key is also logged as error on start ("Encrypted files below … can’t be read").

  • Only files stored after configuring the key are encrypted, files stored before keep their format and stay readable. So configure the key before switching to db: the files copied from the JCR are encrypted right away.

  • The attachments stored in the data base (schema pf_files) aren’t encrypted, only the data transfer files.

  • The costs are low: encryption runs with several GB/s per core (Java 21), compressing a text file of 10 GB takes about a minute.

5.2. Liquidity planning

Supports the cash flow forecast for an easy-to-use liquidity planning. Please refer to the User guide for further information.

5.3. Banking

Bank accounts and the import of bank account statements.

5.4. Software license management

In most companies you’ve to deal with lots of software and license keys. This plugin allows administrators and users to organize license keys:

  • Every user is able to add a software entry with license keys.

  • The administrator is able to view and edit all license keys.

  • Only users who are assigned to a software entry (owners) are able to see the license key.

  • The history of every entry allows to track license usage over time by the users.

  • The history of changes is only visible for administrators and software owners.

5.5. Merlin Word® templates

Templating for Microsoft Word® documents (with variables, dependent variables as well as serial execution), useful for contracts, serial documents etc.

5.6. Skill matrix

Capture and represent the skills of the employees, managed by the users themselves.

5.7. Further plugins

A few more plugins exist for compatibility with existing installations. They are listed under Administration → Plugins as well, but we don’t develop them further.