diff --git a/modules/blocks/authelia.nix b/modules/blocks/authelia.nix
index 5375aa4..89259d6 100644
--- a/modules/blocks/authelia.nix
+++ b/modules/blocks/authelia.nix
@@ -6,6 +6,7 @@ let
   contracts = pkgs.callPackage ../contracts {};
 
   fqdn = "${cfg.subdomain}.${cfg.domain}";
+  fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
 
   autheliaCfg = config.services.authelia.instances.${fqdn};
 
@@ -36,6 +37,12 @@ in
       example = "mydomain.com";
     };
 
+    port = lib.mkOption {
+      description = "If given, adds a port to the `<subdomain>.<domain>` endpoint.";
+      type = lib.types.nullOr lib.types.port;
+      default = null;
+    };
+
     ssl = lib.mkOption {
       description = "Path to SSL files";
       type = lib.types.nullOr contracts.ssl.certs;
@@ -86,7 +93,11 @@ in
           };
           identityProvidersOIDCIssuerPrivateKeyFile = lib.mkOption {
             type = lib.types.path;
-            description = "File containing the identity provider OIDC issuer private key.";
+            description = ''
+              File containing the identity provider OIDC issuer private key.
+
+              Generate one with `nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048`
+            '';
           };
         };
       };
@@ -99,39 +110,47 @@ in
     };
 
     smtp = lib.mkOption {
-      description = "SMTP options.";
-      default = null;
-      type = lib.types.nullOr (lib.types.submodule {
-        options = {
-          from_address = lib.mkOption {
-            type = lib.types.str;
-            description = "SMTP address from which the emails originate.";
-            example = "authelia@mydomain.com";
+      description = ''
+        If a string is given, writes notifications to the given path.Otherwise, send notifications
+        by smtp.
+
+        https://www.authelia.com/configuration/notifications/introduction/
+      '';
+      default = "/tmp/authelia-notifications";
+      type = lib.types.oneOf [
+        lib.types.str
+        (lib.types.nullOr (lib.types.submodule {
+          options = {
+            from_address = lib.mkOption {
+              type = lib.types.str;
+              description = "SMTP address from which the emails originate.";
+              example = "authelia@mydomain.com";
+            };
+            from_name = lib.mkOption {
+              type = lib.types.str;
+              description = "SMTP name from which the emails originate.";
+              default = "Authelia";
+            };
+            host = lib.mkOption {
+              type = lib.types.str;
+              description = "SMTP host to send the emails to.";
+            };
+            port = lib.mkOption {
+              type = lib.types.port;
+              description = "SMTP port to send the emails to.";
+              default = 25;
+            };
+            username = lib.mkOption {
+              type = lib.types.str;
+              description = "Username to connect to the SMTP host.";
+            };
+            passwordFile = lib.mkOption {
+              type = lib.types.str;
+              description = "File containing the password to connect to the SMTP host.";
+            };
           };
-          from_name = lib.mkOption {
-            type = lib.types.str;
-            description = "SMTP name from which the emails originate.";
-            default = "Authelia";
-          };
-          host = lib.mkOption {
-            type = lib.types.str;
-            description = "SMTP host to send the emails to.";
-          };
-          port = lib.mkOption {
-            type = lib.types.port;
-            description = "SMTP port to send the emails to.";
-            default = 25;
-          };
-          username = lib.mkOption {
-            type = lib.types.str;
-            description = "Username to connect to the SMTP host.";
-          };
-          passwordFile = lib.mkOption {
-            type = lib.types.str;
-            description = "File containing the password to connect to the SMTP host.";
-          };
-        };
-      });
+        }))
+      ];
     };
 
     rules = lib.mkOption {
@@ -175,7 +194,7 @@ in
         AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecretFile;
         AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = toString cfg.secrets.identityProvidersOIDCIssuerPrivateKeyFile;
 
-        AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(isNull cfg.smtp)) (toString cfg.smtp.passwordFile);
+        AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(builtins.isString cfg.smtp)) (toString cfg.smtp.passwordFile);
       };
       settings = {
         server.host = "127.0.0.1";
@@ -207,7 +226,7 @@ in
         };
         totp = {
           disable = "false";
-          issuer = fqdn;
+          issuer = fqdnWithPort;
           algorithm = "sha1";
           digits = "6";
           period = "30";
@@ -217,7 +236,7 @@ in
         # Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis
         session = {
           name = "authelia_session";
-          domain = cfg.domain;
+          domain = if isNull cfg.port then cfg.domain else "${cfg.domain}:${toString cfg.port}";
           same_site = "lax";
           expiration = "1h";
           inactivity = "5m";
@@ -238,10 +257,10 @@ in
           };
         };
         notifier = {
-          filesystem = lib.mkIf (isNull cfg.smtp) {
-            filename = "/tmp/authelia-notifications";
+          filesystem = lib.mkIf (builtins.isString cfg.smtp) {
+            filename = cfg.smtp;
           };
-          smtp = lib.mkIf (!(isNull cfg.smtp)) {
+          smtp = lib.mkIf (!(builtins.isString cfg.smtp)) {
             host = cfg.smtp.host;
             port = cfg.smtp.port;
             username = cfg.smtp.username;
@@ -260,7 +279,7 @@ in
           ];
           rules = [
             {
-              domain = fqdn;
+              domain = fqdnWithPort;
               policy = "bypass";
               resources = [
                 "^/api/.*"
diff --git a/modules/blocks/postgresql.nix b/modules/blocks/postgresql.nix
index 391e8a2..cff5999 100644
--- a/modules/blocks/postgresql.nix
+++ b/modules/blocks/postgresql.nix
@@ -50,9 +50,6 @@ in
     let
       commonConfig = {
         services.postgresql.settings = {
-          idle_in_transaction_session_timeout = "30s";
-          idle_session_timeout = "30s";
-          track_io_timing = "true";
         };
       };
 
diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix
index eb5bb3b..6f16d5d 100644
--- a/modules/services/home-assistant.nix
+++ b/modules/services/home-assistant.nix
@@ -197,6 +197,31 @@ in
         shell_command = {
           delete_backups = "find ${config.services.home-assistant.configDir}/backups -type f -delete";
         };
+
+        conversation.intents = {
+          TellJoke = [
+            "Tell [me] (a joke|something funny|a dad joke)"
+            "Raconte [moi] (une blague)"
+          ];
+        };
+        sensor = [
+          {
+            name = "random_joke";
+            platform = "rest";
+            json_attributes = ["joke" "id" "status"];
+            value_template = "{{ value_json.joke }}";
+            resource = "https://icanhazdadjoke.com/";
+            scan_interval = "3600";
+            headers.Accept = "application/json";
+          }
+        ];
+        intent_script.TellJoke = {
+          speech.text = ''{{ state_attr("sensor.random_joke", "joke") }}'';
+          action = {
+            service = "homeassistant.update_entity";
+            entity_id = "sensor.random_joke";
+          };
+        };
       };
     };
 
diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix
index a8ce8fd..ac4dc63 100644
--- a/modules/services/nextcloud-server.nix
+++ b/modules/services/nextcloud-server.nix
@@ -4,6 +4,10 @@ let
   cfg = config.shb.nextcloud;
 
   fqdn = "${cfg.subdomain}.${cfg.domain}";
+  fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
+  protocol = if !(isNull cfg.ssl) then "https" else "http";
+
+  ssoFqdnWithPort = if isNull cfg.apps.sso.port then cfg.apps.sso.endpoint else "${cfg.apps.sso.endpoint}:${toString cfg.apps.sso.port}";
 
   contracts = pkgs.callPackage ../contracts {};
 
@@ -19,16 +23,40 @@ in
 
     subdomain = lib.mkOption {
       type = lib.types.str;
-      description = "Subdomain under which Nextcloud will be served.";
+      description = ''
+        Subdomain under which Nextcloud will be served.
+
+        ```
+        <subdomain>.<domain>[:<port>]
+        ```
+      '';
       example = "nextcloud";
     };
 
     domain = lib.mkOption {
-      description = "Domain to serve Nextcloud under.";
+      description = ''
+        Domain under which Nextcloud is served.
+
+        ```
+        <subdomain>.<domain>[:<port>]
+        ```
+      '';
       type = lib.types.str;
       example = "domain.com";
     };
 
+    port = lib.mkOption {
+      description = ''
+        Port under which Nextcloud will be served. If null is given, then the port is omitted.
+
+        ```
+        <subdomain>.<domain>[:<port>]
+        ```
+      '';
+      type = lib.types.nullOr lib.types.port;
+      default = null;
+    };
+
     ssl = lib.mkOption {
       description = "Path to SSL files";
       type = lib.types.nullOr contracts.ssl.certs;
@@ -168,7 +196,11 @@ in
 
                 jwtSecretFile = lib.mkOption {
                   type = lib.types.nullOr lib.types.path;
-                  description = "File containing the JWT secret. This option is required.";
+                  description = ''
+                    File containing the JWT secret. This option is required.
+
+                    Must be readable by the nextcloud system user.
+                  '';
                   default = null;
                 };
               };
@@ -216,7 +248,7 @@ in
                 enable = lib.mkEnableOption "LDAP app.";
 
                 host = lib.mkOption {
-                  type = lib.types.nullOr lib.types.str;
+                  type = lib.types.str;
                   description = ''
                     Host serving the LDAP server.
                   '';
@@ -224,7 +256,7 @@ in
                 };
 
                 port = lib.mkOption {
-                  type = lib.types.nullOr lib.types.port;
+                  type = lib.types.port;
                   description = ''
                     Port of the service serving the LDAP server.
                   '';
@@ -245,7 +277,11 @@ in
 
                 adminPasswordFile = lib.mkOption {
                   type = lib.types.path;
-                  description = "File containing the admin password of the LDAP server.";
+                  description = ''
+                    File containing the admin password of the LDAP server.
+
+                    Must be readable by the nextcloud system user.
+                  '';
                   default = "";
                 };
 
@@ -257,6 +293,81 @@ in
               };
             });
           };
+
+          sso = lib.mkOption {
+            description = ''
+              SSO Integration App. [Manual](https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/oidc_auth.html)
+
+              Enabling this app will create a new LDAP configuration or update one that exists with
+              the given host.
+            '';
+            default = {};
+            type = lib.types.submodule {
+              options = {
+                enable = lib.mkEnableOption "SSO app.";
+
+                endpoint = lib.mkOption {
+                  type = lib.types.str;
+                  description = "OIDC endpoint for SSO.";
+                  example = "https://authelia.example.com";
+                };
+
+                port = lib.mkOption {
+                  description = "If given, adds a port to the endpoint.";
+                  type = lib.types.nullOr lib.types.port;
+                  default = null;
+                };
+
+                provider = lib.mkOption {
+                  type = lib.types.enum [ "Authelia" ];
+                  description = "OIDC provider name, used for display.";
+                  default = "Authelia";
+                };
+
+                clientID = lib.mkOption {
+                  type = lib.types.str;
+                  description = "Client ID for the OIDC endpoint.";
+                  default = "nextcloud";
+                };
+
+                authorization_policy = lib.mkOption {
+                  type = lib.types.enum [ "one_factor" "two_factor" ];
+                  description = "Require one factor (password) or two factor (device) authentication.";
+                  default = "one_factor";
+                };
+
+                secretFile = lib.mkOption {
+                  type = lib.types.path;
+                  description = ''
+                    File containing the secret for the OIDC endpoint.
+
+                    Must be readable by the nextcloud system user.
+                  '';
+                  default = "";
+                };
+
+                secretFileForAuthelia = lib.mkOption {
+                  type = lib.types.path;
+                  description = ''
+                    File containing the secret for the OIDC endpoint, must be readable by the Authelia user.
+
+                    Must be readable by the authelia system user.
+                  '';
+                  default = "";
+                };
+
+                fallbackDefaultAuth = lib.mkOption {
+                  type = lib.types.bool;
+                  description = ''
+                    Fallback to normal Nextcloud auth if something goes wrong with the SSO app.
+                    Usually, you want to enable this to transfer existing users to LDAP and then you
+                    can disabled it.
+                  '';
+                  default = false;
+                };
+              };
+            };
+          };
         };
       };
     };
@@ -387,7 +498,7 @@ in
           protocol = if !(isNull cfg.ssl) then "https" else "http";
         in {
           "overwrite.cli.url" = "${protocol}://${fqdn}";
-          "overwritehost" = if (isNull cfg.externalFqdn) then fqdn else cfg.externalFqdn;
+          "overwritehost" = fqdnWithPort;
           # 'trusted_domains' needed otherwise we get this issue https://help.nextcloud.com/t/the-polling-url-does-not-start-with-https-despite-the-login-url-started-with-https/137576/2
           # TODO: could instead set extraTrustedDomains
           "trusted_domains" = [ fqdn ];
@@ -450,8 +561,8 @@ in
         # [1]: https://help.nextcloud.com/t/download-aborts-after-time-or-large-file/25044/6
         # [2]: https://stackoverflow.com/a/50891625/1013628
         extraConfig = ''
-      proxy_buffering off;
-      '';
+        proxy_buffering off;
+        '';
       };
 
       environment.systemPackages = [
@@ -470,7 +581,7 @@ in
       };
       systemd.services.phpfpm-nextcloud.preStart = ''
       mkdir -p /var/log/xdebug; chown -R nextcloud: /var/log/xdebug
-    '';
+      '';
 
       systemd.services.nextcloud-cron.path = [
         pkgs.perl
@@ -514,8 +625,8 @@ in
 
         locations."/" = {
           extraConfig = ''
-        allow ${cfg.apps.onlyoffice.localNetworkIPRange};
-        '';
+          allow ${cfg.apps.onlyoffice.localNetworkIPRange};
+          '';
         };
       };
     })
@@ -620,5 +731,103 @@ in
                   '1'
       '';
     })
+
+    (lib.mkIf cfg.apps.sso.enable {
+      assertions = [
+        {
+          assertion = cfg.apps.sso.enable -> cfg.apps.ldap.enable;
+          message = "SSO app requires LDAP app to work correctly.";
+        }
+      ];
+
+      systemd.services.nextcloud-setup.script =
+        ''
+        ${occ} app:install oidc_login || :
+        ${occ} app:enable  oidc_login
+        '';
+
+      systemd.services.nextcloud-setup.preStart =
+        ''
+        mkdir -p ${cfg.dataDir}/config
+        cat <<EOF > "${cfg.dataDir}/config/secretFile"
+        {
+          "oidc_login_client_secret": "$(cat ${cfg.apps.sso.secretFile})"
+        }
+        EOF
+        '';
+
+      services.nextcloud = {
+        secretFile = "${cfg.dataDir}/config/secretFile";
+
+        # See all options at https://github.com/pulsejet/nextcloud-oidc-login
+        extraOptions = {
+          allow_user_to_change_display_name = false;
+          lost_password_link = "disabled";
+          oidc_login_provider_url = ssoFqdnWithPort;
+          oidc_login_client_id = cfg.apps.sso.clientID;
+
+          # Automatically redirect the login page to the provider.
+          oidc_login_auto_redirect = !cfg.apps.sso.fallbackDefaultAuth;
+          # Authelia at least does not support this.
+          oidc_login_end_session_redirect = false;
+          # Redirect to this page after logging out the user
+          oidc_login_logout_url = ssoFqdnWithPort;
+          oidc_login_button_text = "Log in with ${cfg.apps.sso.provider}";
+          oidc_login_hide_password_form = false;
+          oidc_login_use_id_token = true;
+          oidc_login_attributes = {
+            id = "preferred_username";
+            name = "name";
+            mail = "email";
+            groups = "groups";
+          };
+          oidc_login_default_group = "oidc";
+          oidc_login_use_external_storage = false;
+          oidc_login_scope = "openid profile email groups";
+          oidc_login_proxy_ldap = false;
+          # Enable creation of users new to Nextcloud from OIDC login. A user may be known to the
+          # IdP but not (yet) known to Nextcloud. This setting controls what to do in this case.
+          # * 'true' (default): if the user authenticates to the IdP but is not known to Nextcloud,
+          #     then they will be returned to the login screen and not allowed entry;
+          # * 'false': if the user authenticates but is not yet known to Nextcloud, then the user
+          #     will be automatically created; note that with this setting, you will be allowing (or
+          #     relying on) a third-party (the IdP) to create new users
+          oidc_login_disable_registration = false;
+          oidc_login_redir_fallback = cfg.apps.sso.fallbackDefaultAuth;
+          # oidc_login_alt_login_page = "assets/login.php";
+          oidc_login_tls_verify = true;
+          # If you get your groups from the oidc_login_attributes, you might want to create them if
+          # they are not already existing, Default is `false`.
+          oidc_create_groups = true;
+          # Enable use of WebDAV via OIDC bearer token.
+          oidc_login_webdav_enabled = true;
+          oidc_login_password_authentication = false;
+          oidc_login_public_key_caching_time = 86400;
+          oidc_login_min_time_between_jwks_requests = 10;
+          oidc_login_well_known_caching_time = 86400;
+          # If true, nextcloud will download user avatars on login. This may lead to security issues
+          # as the server does not control which URLs will be requested. Use with care.
+          oidc_login_update_avatar = false;
+        };
+      };
+
+      shb.authelia.oidcClients = lib.mkIf (cfg.apps.sso.provider == "Authelia") [
+        {
+          id = cfg.apps.sso.clientID;
+          description = "Nextcloud";
+          secretFile = cfg.apps.sso.secretFileForAuthelia;
+          public = "false";
+          authorization_policy = cfg.apps.sso.authorization_policy;
+          redirect_uris = [ "${protocol}://${fqdnWithPort}/apps/oidc_login/oidc" ];
+          scopes = [
+            "openid"
+            "profile"
+            "email"
+            "groups"
+          ];
+          userinfo_signing_algorithm = "none";
+        }
+      ];
+    })
   ];
 }
diff --git a/modules/services/nextcloud-server/docs/default.md b/modules/services/nextcloud-server/docs/default.md
index 5ffcb37..cf0c122 100644
--- a/modules/services/nextcloud-server/docs/default.md
+++ b/modules/services/nextcloud-server/docs/default.md
@@ -9,6 +9,7 @@ This NixOS module is a service that sets up a [Nextcloud Server](https://nextclo
 - Declarative [Apps](#services-nextcloud-server-options-shb.nextcloud.apps) Configuration - no need
   to configure those with the UI.
   - [LDAP](#services-nextcloud-server-usage-ldap) app: enables app and sets up integration with an existing LDAP server.
+  - [OIDC](#services-nextcloud-server-usage-oidc) app: enables app and sets up integration with an existing OIDC server.
   - [Preview Generator](#services-nextcloud-server-usage-previewgenerator) app: enables app and sets
     up required cron job.
   - [Only Office](#services-nextcloud-server-usage-onlyoffice) app: enables app and sets up Only
@@ -35,9 +36,36 @@ This NixOS module is a service that sets up a [Nextcloud Server](https://nextclo
 
 ## Usage {#services-nextcloud-server-usage}
 
-### Basic Configuration {#services-nextcloud-server-usage-basic}
+### Secrets {#services-nextcloud-server-secrets}
 
-This section corresponds to the `basic` target host defined in the [flake.nix](./flake.nix) file.
+All the secrets should be readable by the nextcloud user.
+
+Secret should not be stored in the nix store. If you're using
+[sops-nix](https://github.com/Mic92/sops-nix) and assuming your secrets file is located at
+`./secrets.yaml`, you can define a secret with:
+
+```nix
+sops.secrets."nextcloud/adminpass" = {
+  sopsFile = ./secrets.yaml;
+  mode = "0400";
+  owner = "nextcloud";
+  group = "nextcloud";
+  restartUnits = [ "phpfpm-nextcloud.service" ];
+};
+```
+
+Then you can use that secret:
+
+```nix
+shb.nextcloud.adminPassFile = config.sops.secrets."nextcloud/adminpass".path;
+```
+
+### Nextcloud through HTTP {#services-nextcloud-server-usage-basic}
+
+:::: {.note}
+This section corresponds to the `basic` section of the [Nextcloud
+demo](demo-nextcloud-server.html#demo-nextcloud-deploy-basic).
+::::
 
 This will set up a Nextcloud service that runs on the NixOS target machine, reachable at
 `http://nextcloud.example.com`. If the `shb.ssl` block is [enabled](block-ssl.html#usage), the
@@ -53,27 +81,19 @@ shb.nextcloud = {
 };
 ```
 
-The secret should not be stored in the nix store. If you're using
-[sops-nix](https://github.com/Mic92/sops-nix) and assuming your secrets file is located at
-`./secrets.yaml`, you can set the `adminPassFile` option with:
-
-```nix
-shb.nextcloud.adminPassFile = config.sops.secrets."nextcloud/adminpass".path;
-
-sops.secrets."nextcloud/adminpass" = {
-  sopsFile = ./secrets.yaml;
-  mode = "0400";
-  owner = "nextcloud";
-  group = "nextcloud";
-  restartUnits = [ "phpfpm-nextcloud.service" ];
-};
-```
+After deploying, the Nextcloud server will be reachable at `http://nextcloud.example.com`.
 
 ### With LDAP Support {#services-nextcloud-server-usage-ldap}
 
-This section corresponds to the `ldap` target host defined in the [flake.nix](./flake.nix) file. The same information from the [basic](#services-nextcloud-server-usage-basic) section applies, so please read that first.
+:::: {.note}
+This section corresponds to the `ldap` section of the [Nextcloud
+demo](demo-nextcloud-server.html#demo-nextcloud-deploy-ldap).
+::::
 
-This target host uses the LDAP block provided by Self Host Blocks to setup a
+We will build upon the [Basic Configuration](#services-nextcloud-server-usage-basic) section, so
+please read that first.
+
+We will use the LDAP block provided by Self Host Blocks to setup a
 [LLDAP](https://github.com/lldap/lldap) service.
 
 ```nix
@@ -84,24 +104,8 @@ shb.ldap = {
   ldapPort = 3890;
   webUIListenPort = 17170;
   dcdomain = "dc=example,dc=com";
-  ldapUserPasswordFile = config.sops.secrets."lldap/user_password".path;
-  jwtSecretFile = config.sops.secrets."lldap/jwt_secret".path;
-};
-
-sops.secrets."lldap/user_password" = {
-  sopsFile = ./secrets.yaml;
-  mode = "0440";
-  owner = "lldap";
-  group = "lldap";
-  restartUnits = [ "lldap.service" ];
-};
-
-sops.secrets."lldap/jwt_secret" = {
-  sopsFile = ./secrets.yaml;
-  mode = "0440";
-  owner = "lldap";
-  group = "lldap";
-  restartUnits = [ "lldap.service" ];
+  ldapUserPasswordFile = <path/to/ldapUserPasswordSecret>;
+  jwtSecretFile = <path/to/ldapJwtSecret>;
 };
 ```
 
@@ -115,12 +119,110 @@ shb.nextcloud.apps.ldap
   port = config.shb.ldap.ldapPort;
   dcdomain = config.shb.ldap.dcdomain;
   adminName = "admin";
-  adminPasswordFile = config.sops.secrets."nextcloud/ldap_admin_password".path;
+  adminPasswordFile = <path/to/ldapUserPasswordSecret>;
   userGroup = "nextcloud_user";
 };
 ```
 
-It's nice to be able to reference a options that were defined in the ldap block.
+The `shb.nextcloud.apps.ldap.adminPasswordFile` must be the same as the
+`shb.ldap.ldapUserPasswordFile`. The other secret can be randomly generated with `nix run
+nixpkgs#openssl -- rand -hex 64`.
+
+And that's it. Now, go to the LDAP server at `http://ldap.example.com`, create the `nextcloud_user`
+group, create a user and add it to the group. When that's done, go back to the Nextcloud server at
+`http://nextcloud.example.com` and login with that user.
+
+Note that we cannot create an admin user from the LDAP server, so you need to create a normal user
+like above, login with it once so it is known to Nextcloud, then logout, login with the admin
+Nextcloud user and promote that new user to admin level.
+
+### With OIDC Support {#services-nextcloud-server-usage-oidc}
+
+:::: {.note}
+This section corresponds to the `sso` section of the [Nextcloud
+demo](demo-nextcloud-server.html#demo-nextcloud-deploy-sso).
+::::
+
+We will build upon the [Basic Configuration](#services-nextcloud-server-usage-basic) and [With LDAP
+Support](#services-nextcloud-server-usage-ldap) sections, so please read those first and setup the
+LDAP app as described above.
+
+Here though, we must setup SSL certificates because the SSO provider only works with the https
+protocol. This is actually quite easy thanks to the [SSL block](blocks-ssl.html). For example, with
+self-signed certificates:
+
+```nix
+shb.certs = {
+  cas.selfsigned.myca = {
+    name = "My CA";
+  };
+  certs.selfsigned = {
+    nextcloud = {
+      ca = config.shb.certs.cas.selfsigned.myca;
+      domain = "nextcloud.example.com";
+    };
+    auth = {
+      ca = config.shb.certs.cas.selfsigned.myca;
+      domain = "auth.example.com";
+    };
+    ldap = {
+      ca = config.shb.certs.cas.selfsigned.myca;
+      domain = "ldap.example.com";
+    };
+  };
+};
+```
+
+We need to setup the SSO provider, here Authelia thanks to the corresponding SHB block:
+
+```nix
+shb.authelia = {
+  enable = true;
+  domain = "example.com";
+  subdomain = "auth";
+  ssl = config.shb.certs.certs.selfsigned.auth;
+
+  ldapEndpoint = "ldap://127.0.0.1:${builtins.toString config.shb.ldap.ldapPort}";
+  dcdomain = config.shb.ldap.dcdomain;
+
+  secrets = {
+    jwtSecretFile = <path/to/autheliaJwtSecret>;
+    ldapAdminPasswordFile = <path/to/ldapUserPasswordSecret>;
+    sessionSecretFile = <path/to/autheliaSessionSecret>;
+    storageEncryptionKeyFile = <path/to/autheliaStorageEncryptionKeySecret>;
+    identityProvidersOIDCHMACSecretFile = <path/to/providersOIDCHMACSecret>;
+    identityProvidersOIDCIssuerPrivateKeyFile = <path/to/providersOIDCIssuerSecret>;
+  };
+};
+```
+
+The `shb.authelia.secrets.ldapAdminPasswordFile` must be the same as the
+`shb.ldap.ldapUserPasswordFile` defined in the previous section. The secrets can be randomly
+generated with `nix run nixpkgs#openssl -- rand -hex 64`.
+
+Now, on the Nextcloud side, you need to add the following options:
+
+```nix
+shb.nextcloud.ssl = config.shb.certs.certs.selfsigned.nextcloud;
+
+shb.nextcloud.apps.sso = {
+  enable = true;
+  endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
+  clientID = "nextcloud";
+  fallbackDefaultAuth = false;
+
+  secretFile = <path/to/oidcNextcloudSharedSecret>;
+  secretFileForAuthelia = <path/to/oidcNextcloudSharedSecret>;
+};
+```
+
+Passing the `ssl` option will auto-configure nginx to force SSL connections with the given
+certificate.
+
+The `shb.nextcloud.apps.sso.secretFile` and `shb.nextcloud.apps.sso.secretFileForAuthelia` options
+must have the same content. The former is a file that must be owned by the `nextcloud` user while
+the latter must be owned by the `authelia` user. I want to avoid needing to define the same secret
+twice with a future secrets SHB block.
 
 ### Tweak PHPFpm Config {#services-nextcloud-server-usage-phpfpm}
 
@@ -139,6 +241,8 @@ shb.nextcloud.phpFpmPoolSettings = {
 
 ### Tweak PostgreSQL Settings {#services-nextcloud-server-usage-postgres}
 
+These settings will impact all databases.
+
 ```nix
 shb.nextcloud.postgresSettings = {
   max_connections = "100";
@@ -227,6 +331,19 @@ without LDAP integration on a VM with minimal manual steps.
 
 On the command line, the `occ` tool is called `nextcloud-occ`.
 
+## Debug {#services-nextcloud-server-debug}
+
+In case of an issue, check the logs for any systemd service mentioned in this section.
+
+On startup, the oneshot systemd service `nextcloud-setup.service` starts. After it finishes, the
+`phpfpm-nextcloud.service` starts to serve Nextcloud. The `nginx.service` is used as the reverse
+proxy. `postgresql.service` run the database.
+
+Nextcloud' configuration is found at `${shb.nextcloud.dataDir}/config/config.php`. Nginx'
+configuration can be found with `systemctl cat nginx | grep -om 1 -e "[^ ]\+conf"`.
+
+Enable verbose logging by setting the `shb.nextcloud.debug` boolean to `true`.
+
 ## Options Reference {#services-nextcloud-server-options}
 
 ```{=include=} options
diff --git a/test/modules/postgresql.nix b/test/modules/postgresql.nix
index 863fa75..961616d 100644
--- a/test/modules/postgresql.nix
+++ b/test/modules/postgresql.nix
@@ -24,9 +24,9 @@ let
     };
 
   commonSettings = {
-    idle_in_transaction_session_timeout = "30s";
-    idle_session_timeout = "30s";
-    track_io_timing = "true";
+    # idle_in_transaction_session_timeout = "30s";
+    # idle_session_timeout = "30s";
+    # track_io_timing = "true";
   };
 in
 {
diff --git a/test/vm/nextcloud.nix b/test/vm/nextcloud.nix
index 4df22e5..72477ec 100644
--- a/test/vm/nextcloud.nix
+++ b/test/vm/nextcloud.nix
@@ -16,6 +16,7 @@ in
           options = {
             shb.ssl.enable = lib.mkEnableOption "ssl";
             shb.backup = lib.mkOption { type = lib.types.anything; };
+            shb.authelia = lib.mkOption { type = lib.types.anything; };
           };
         }
         # ../../modules/blocks/authelia.nix