Administration guide
Hints, tips and best practices for the administration of ProjectForge
- 1. Introduction
- 2. ProjectForge Setup
- 3. Configuration
- 3.1. Base directory (application’s home)
- 3.2. Configuration file application.properties
- 3.3. Switching off functions
- 3.4. Customization
- 3.5. Configuration settings (web interface)
- 3.6. Using ttf fonts for PDF render engine
- 3.7. HTML formatted E-Mail templates
- 3.8. Sending e-mails
- 3.9. Support error digest
- 3.10. System dashboard and log viewer
- 3.11. Phone calls and texting with ProjectForge
- 3.12. Number ranges of customers, projects and cost units
- 3.13. Group nomenclature
- 3.14. business assessment / BWA
- 3.15. SecurityConfig
- 3.16. LDAP
- 3.17. OAuth2 / OIDC Authentication
- 3.18. Identity Provider Sync (IdP)
- 3.19. Troubleshooting
- 4. Maintenance
- 5. Plugins
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
-
Extract:
keytool -exportcert -alias selfsigned -keystore keystore.p12 -storetype PKCS12 -file selfsigned.crt -rfc -
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.
-
Create the user (e.g. as admin user):
createuser -U postgres -P projectforge -
Create the database:
createdb -U postgres -E UTF-8 -O projectforge projectforge -
You can drop an existing user and database with:
dropdb projectforgeanddropuser projectforge -
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:
|
Configuration file, written by the setup wizard. A former |
|
The embedded database (HSQLDB), if used. |
|
The search indices (Lucene). If corrupted, stop ProjectForge, delete the content of this directory and rebuild the indices via Administration → System → Reindex. |
|
The log files, e.g. |
|
Your own resources, overriding the built-in ones: |
|
Default directory for backups. |
|
The files of the data transfer areas (plugin Data transfer). |
|
The former document repository (JCR), only for installations not yet migrated, see Moving the files out of the JCR. |
|
Working directory, e.g. for states of synchronizations. |
|
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:
-
Create one or more properties files in the
~/ProjectForge/resourcesdirectory:-
CustomerI18nResources.properties- for default/English translations -
CustomerI18nResources_de.properties- for German translations -
CustomerI18nResources_en.properties- for English translations (explicit)
-
-
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
-
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.propertiesfiles 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.
projectforge.propertiesprojectforge.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=
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.*
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 |
|---|---|
|
At least one range is configured. Only the configured ranges apply. |
|
No range is configured, but Kost2 of projects in the formerly built-in ranges exist: 4 for internal
projects ( |
|
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 |
|---|---|---|
|
First digit (1-9) of the project and Kost2 numbers of this range, unique for all ranges. |
|
|
Display name, e.g. |
|
|
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 |
|
|
2 |
Number of digits of the project number per customer (1-5), or of the whole range for a range without customers. |
|
|
Segments of the 8-digit Kost2 for display and input, e.g. |
Global settings:
| Property | Default | Description |
|---|---|---|
|
|
If |
|
|
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.
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
FREEcan’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.for1.#..). Withprojectforge.invoicing-quota.billed-by-invoiced-kost2-types=truethe 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:
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/usermatches. -
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_ACCOUNTandPASSWORD, 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.
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:
projectforge.properties1
2
3
...
projectforge.login.handlerClass=LdapMasterLoginHandler
...
-
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.
-
Any change of the LDAP server will be ignored and may be overwritten by ProjectForge.
-
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". -
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. -
Deleted Users
Deleted users will not be synchronized and removed in LDAP if exist. -
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). -
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. -
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. -
Groups and assigned users
Users will be added asuniqueMemberentries 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.
Figure 3: |
ProjectForge uses the fields and object classes shown in
this figure for users. The object class |
Figure 4: |
ProjectForge uses the fields and object classes shown in
this figure for groups. The field |
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:
Figure 5: |
ProjectForge is able to manage POSIX accounts in LDAP master mode. |
-
After clicking the Button
createbeside the UID number field ProjectForge will prefill all POSIX account fields:-
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.
-
The GID number will be preset with
projectforge.ldap.posixAccountsDefaultGidNumberconfigured inprojectforge.properties. -
The home directory will be the prefix
/home/followed by the user-name: e. g./home/jdoe(not configurable). -
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-classposixAccountonly if the POSIX account data were created in ProjectForge for this user.
3.16.6. Managing Samba accounts
projectforge.properties1
2
3
4
...
projectforge.ldap.sambaAccountsSIDPrefix=
projectforge.ldap.sambaAccountsPrimaryGroupSID=
...
Figure 6: |
ProjectForge is able to manage POSIX and Samba accounts in LDAP master mode. |
-
After clicking the Button
createbeside the UID number field ProjectForge will prefill all SAMBA account fields:-
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.
-
The GID number will be preset with the defaultSambaPrimaryGroupSID configured in
projectforge.properties. -
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-classsambaSamAccountonly 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:
projectforge.properties1
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).
projectforge.properties1
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:
-
Match the OIDC
subclaim against theidpExternalIdfield of ProjectForge users -
If no match is found, fall back to matching the
preferred_usernameclaim against the ProjectForge username -
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 |
|---|---|
|
Enable periodic sync push to the gateway instance |
|
Base URL of the gateway’s sync API endpoint |
|
Shared secret (must match |
|
Interval between sync pushes in milliseconds (default: 900000 = 15 minutes) |
|
Delay of the first sync push after start-up in milliseconds (default: 120000 = 2 minutes) |
|
URL of the gateway for browsers (external links of data transfer areas), only if it differs from |
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
-
Create a realm (e.g.
acme.com) -
Enable locales globally:
de,en -
Create a client
projectforge(confidential, service account enabled) -
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 |
|---|---|---|---|
|
0 |
100 |
|
|
0 |
255 |
|
|
Options: |
||
|
0 |
— |
Validated automatically ( |
|
0 |
255 |
|
|
0 |
255 |
|
|
0 |
255 |
|
|
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
-
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) -
Create a sync user in Admin with this role
-
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 |
|---|---|
|
No external IdP (default) |
|
ProjectForge is master; users/groups are pushed PF → IdP |
|
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 schemapf_files). -
The dump of the files only:
pg_dump -n pf_files -Fc …(custom format, restorable in parallel bypg_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
jcror roll back to the former release; only files uploaded underdbare 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 (schemapf_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:
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):
pg_hba.conf1
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:
-
Generate keys (using empty password) ssh-keygen
-
Copy your public key, e. g.
identity.pubto your backup server. -
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:
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 variablePROJECTFORGE_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.