Compare commits

...

33 Commits

Author SHA1 Message Date
Thomas Gräfenstein
2281ebcb6d improved container restart alert 2026-03-22 23:56:27 +01:00
Thomas Gräfenstein
2942ff15bc remove unused /dev/kmsg device mount from cAdvisor (oom_event is disabled) 2026-03-22 23:34:29 +01:00
Thomas Gräfenstein
24e80de43c upgrade cAdvisor to v0.54.1 for Docker 29 containerd image store support 2026-03-22 23:30:24 +01:00
Thomas Gräfenstein
cfc8b61f98 connect cAdvisor to containerd socket for Docker 29 image store compatibility 2026-03-22 23:24:50 +01:00
Thomas Gräfenstein
b063128049 grant cAdvisor privileged access for cgroup v2 container discovery 2026-03-22 23:17:33 +01:00
Thomas Gräfenstein
a07adedd00 fix cAdvisor container discovery by mounting /sys and /var/lib/docker correctly 2026-03-22 23:14:32 +01:00
Thomas Gräfenstein
31705ad888 fix cAdvisor crash by removing unsupported accelerator metric group 2026-03-22 23:06:34 +01:00
Thomas Gräfenstein
b5c5c11114 ensure monitoring stack starts before all other services 2026-03-22 22:55:42 +01:00
Thomas Gräfenstein
926766346c add cAdvisor and document detailed alert queries in README
Add cAdvisor container to the monitoring stack for container-level
metrics. Configure Alloy to scrape cAdvisor. Expand the README
Recommended Alerts section with exact PromQL/LogQL queries, thresholds,
and Grafana alert rule configuration for all five alerts.
2026-03-22 22:51:22 +01:00
Thomas Gräfenstein
c736c23e9a enable NETWORKS in docker-socket-proxy for Alloy container discovery 2026-03-22 21:27:26 +01:00
Thomas Gräfenstein
a02f33e96e move text compression from Caddy to nginx for lower latency
Nginx is closer to the origin, so compressing there avoids an
extra hop. Removes the Caddy encode block for Nextcloud and adds
gzip in nginx with level 4 targeting text, CSS, JS, JSON, XML, SVG.
2026-03-22 21:08:40 +01:00
Thomas Gräfenstein
d62b627093 add .mjs MIME type to nginx to fix NS_ERROR_CORRUPTED_CONTENT
nginx doesn't know .mjs by default and serves it as
application/octet-stream, which breaks ES module loading
and causes Caddy compression mismatches.
2026-03-22 20:56:10 +01:00
Thomas Gräfenstein
fb1de4f079 limit Caddy compression to text content types to fix slow file downloads
Caddy was compressing all responses including binary file downloads
(PDFs, images, videos), which severely throttled download speed to
~130KB/s despite 30MB/s VPS bandwidth. Now only compresses text-based
types (HTML, CSS, JS, JSON, XML, SVG) where compression actually helps.
2026-03-22 20:26:03 +01:00
Thomas Gräfenstein
3bf80f6940 disable file compression temporary 2026-03-22 20:20:37 +01:00
Thomas Gräfenstein
1c2fb3c807 fix nginx redirect loop 2026-03-22 18:12:18 +01:00
Thomas Gräfenstein
b918e713e5 align nginx and Caddy config with official Nextcloud docs
Move security headers to Caddy (edge proxy), remove nginx gzip
(Caddy already compresses), add asset_immutable map for versioned
cache control, add missing static file extensions, fix .well-known
block, and hide X-Powered-By header.
2026-03-22 17:58:26 +01:00
Thomas Gräfenstein
ac3bff9351 fix nginx to fall through to PHP for dynamic assets like theming CSS
Static file locations were returning hard 404s instead of falling
through to PHP, which broke dynamically generated assets like
theming CSS files.
2026-03-22 17:49:45 +01:00
Thomas Gräfenstein
0088c11d5e enable Caddy response compression to fix slow page loads
Caddy was decompressing nginx's gzip responses and sending them
uncompressed to the browser, causing core-common.js (5.7MB) to
take 25s to download. Adding encode zstd gzip compresses it to
1.3MB at the edge.
2026-03-22 17:43:24 +01:00
Thomas Gräfenstein
4f3f4b0487 add swap check command before setup instructions 2026-03-22 17:33:11 +01:00
Thomas Gräfenstein
a51f86ea0a add swap setup instructions to README prerequisites 2026-03-22 17:32:48 +01:00
Thomas Gräfenstein
22198784d3 tune PHP and FPM for 1-core/3GB VPS performance
Reduce FPM workers from 12 to 5 max to stop memory thrashing on
a single-core VPS with 3GB RAM. Add OPcache and APCu tuning to
reduce filesystem stat calls and improve cache hit rates.
2026-03-22 17:31:14 +01:00
Thomas Gräfenstein
0a305a47b9 gitignore claude local settings 2026-03-22 17:21:13 +01:00
Thomas Gräfenstein
d88a8db9f1 fix nginx rewrite loop causing slow page loads and 500 errors
Static file locations now return 404 instead of falling through to
index.php, and the default location uses a clean rewrite to prevent
/index.php/index.php redirect cycles.
2026-03-22 17:19:34 +01:00
Thomas Gräfenstein
995dfcc099 add FPM worker tuning and architecture diagram
Increase PHP-FPM max_children from 5 to 12 to handle concurrent
requests without queuing, sized for a ~3GB VPS. Add Mermaid
architecture diagram to README.
2026-03-22 17:07:43 +01:00
Thomas Gräfenstein
4329cfd3f2 switch nextcloud to FPM + Nginx for better static file performance
Replace the all-in-one Apache image with nextcloud:33-fpm and an Nginx
sidecar that serves static assets directly with gzip compression and
cache headers, avoiding the prefork concurrency bottleneck.
2026-03-22 17:00:33 +01:00
Thomas Gräfenstein
c0c20a42ed add gzip/zstd compression and Redis caching for Nextcloud performance 2026-03-22 16:47:02 +01:00
Thomas Gräfenstein
a17c63a39b remove nextcloud review, all issues fixed 2026-03-22 16:44:15 +01:00
Thomas Gräfenstein
cdec4e3e22 fix trusted_proxies to use CIDR instead of hostname 2026-03-22 16:39:54 +01:00
Thomas Gräfenstein
0e0a6ff1eb add trusted proxy, post-install/upgrade hooks, occ docs and admin review
- Add TRUSTED_PROXIES=caddy to fix reverse proxy header warning
- Add post-installation hook: maintenance window, phone region, DB indices, MIME migrations
- Add post-upgrade hook: DB indices and MIME migrations
- Add occ commands section to README
- Add nextcloud-review.md with admin warning fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 16:33:40 +01:00
Thomas Gräfenstein
7225f526da enhanced readme 2026-03-22 16:14:19 +01:00
Thomas Gräfenstein
8b5c9bdbfc bump nextcloud versions 2026-03-22 16:07:18 +01:00
Thomas Gräfenstein
770081397c enhanced readme 2026-03-22 16:00:55 +01:00
Thomas Gräfenstein
8f5b73dffc fix readme and script 2026-03-22 15:46:29 +01:00
15 changed files with 442 additions and 55 deletions

View File

@@ -1,30 +0,0 @@
{
"permissions": {
"allow": [
"WebSearch",
"WebFetch(domain:docs.docker.com)",
"WebFetch(domain:hub.docker.com)",
"WebFetch(domain:caddyserver.com)",
"WebFetch(domain:docs.nextcloud.com)",
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(git status:*)"
],
"deny": [
"Bash(ssh:*)",
"Bash(rm -rf:*)",
"Bash(docker system prune:*)",
"Bash(docker volume rm:*)",
"Bash(docker compose down:*)",
"Bash(docker stop:*)",
"Bash(docker rm:*)",
"Bash(scp:*)",
"Bash(rsync:*)",
"Bash(curl -X POST:*)",
"Bash(curl -X DELETE:*)",
"Bash(git push:*)",
"Bash(git reset --hard:*)",
"Bash(git clean:*)"
]
}
}

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.env
.idea/
*.iml
.claude/settings.local.json

234
README.md
View File

@@ -2,9 +2,51 @@
Docker Compose setup for a self-hosted VPS running Nextcloud, Gitea, and monitoring — managed as a GitOps-style repo.
## Architecture
```mermaid
graph TB
Internet([Internet])
subgraph VPS["VPS (t-gstone.de)"]
subgraph proxy_net["proxy network"]
Caddy["Caddy<br/>reverse proxy + auto HTTPS"]
end
subgraph nc_stack["Nextcloud Stack"]
Nginx["Nginx<br/>static files + FastCGI proxy"]
NC["Nextcloud FPM<br/>PHP processing"]
Cron["Cron<br/>background jobs"]
PG["PostgreSQL 17"]
Redis["Redis 8"]
end
subgraph gitea_stack["Gitea Stack"]
Gitea["Gitea<br/>rootless, SQLite"]
end
subgraph mon_stack["Monitoring Stack"]
Alloy["Grafana Alloy"]
end
end
GrafanaCloud([Grafana Cloud])
Internet -->|":443 HTTPS"| Caddy
Internet -->|":2222 SSH"| Gitea
Caddy -->|"nextcloud.t-gstone.de"| Nginx
Caddy -->|"git.t-gstone.de"| Gitea
Nginx -->|":9000 FastCGI"| NC
NC --> PG
NC --> Redis
Cron --> PG
Cron --> Redis
Alloy -->|"logs + metrics"| GrafanaCloud
```
## Prerequisites
- A VPS with SSH access
- A VPS with SSH access (minimum 1 core, 3 GB RAM)
- Domain `t-gstone.de` with DNS control
- Git installed locally
@@ -14,6 +56,24 @@ Check your VPS OS:
cat /etc/os-release
```
### Swap (recommended)
Check current memory and swap:
```bash
free -h
```
If swap shows `0B`, add a 2 GB swapfile to prevent OOM kills:
```bash
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
```
## DNS Setup
Create these A records pointing to your VPS IP:
@@ -54,6 +114,62 @@ ssh-add --apple-use-keychain ~/.ssh/id_ed25519
After this, `ssh t-gstone.de` connects without any password prompts.
## Syncing to the VPS
If the VPS doesn't have a git remote set up yet, use `rsync` over SSH to push the repo:
```bash
# Sync the repo to the VPS (dry-run first to check what would be sent)
rsync -avz --dry-run -e 'ssh -p 55' \
--exclude '.env' --exclude '*/.env' --exclude '.git' \
./ gstone@t-gstone.de:~/nextcloud-selfhosted/
# Run for real (remove --dry-run)
rsync -avz -e 'ssh -p 55' \
--exclude '.env' --exclude '*/.env' --exclude '.git' \
./ gstone@t-gstone.de:~/nextcloud-selfhosted/
```
The `.env` files are excluded because they contain secrets and should be created directly on the VPS from the `.env.example` templates.
## Syncing via Git (recommended)
Now that Gitea is running, you can clone the repo directly on the VPS instead of using rsync.
### First-time setup on the VPS
```bash
# 1. Add your SSH key to Gitea (via https://git.t-gstone.de user settings)
# 2. Clone the repo
cd ~
git clone ssh://git@git.t-gstone.de:2222/gstone/nextcloud-selfhosted.git
cd nextcloud-selfhosted
# 3. Create .env files from examples
cp .env.example .env
cp nextcloud/.env.example nextcloud/.env
cp gitea/.env.example gitea/.env
cp monitoring/.env.example monitoring/.env
# 4. Edit each .env file with real values
nano .env
nano nextcloud/.env
nano gitea/.env
nano monitoring/.env
# 5. Deploy
sudo ./scripts/deploy.sh
```
### Updating the VPS after pushing changes
```bash
cd ~/nextcloud-selfhosted
git pull
sudo docker compose --env-file .env up -d
```
## Quick Start
```bash
@@ -116,8 +232,28 @@ docker compose --env-file .env down
You can still target individual services via their compose file:
```bash
docker compose -f nextcloud/docker-compose.yml --env-file .env up -d
docker compose -f gitea/docker-compose.yml --env-file .env logs -f
docker compose --env-file .env -f caddy/docker-compose.yml up -d
docker compose --env-file .env -f gitea/docker-compose.yml up -d
docker compose --env-file .env -f monitoring/docker-compose.yml up -d
docker compose --env-file .env -f nextcloud/docker-compose.yml up -d
docker compose --env-file .env -f gitea/docker-compose.yml logs -f
```
## Running Nextcloud OCC Commands
Nextcloud's `occ` command-line tool must run as the `www-data` user inside the container:
```bash
# General syntax
sudo docker exec -u www-data nextcloud php occ <command>
# Examples
sudo docker exec -u www-data nextcloud php occ status
sudo docker exec -u www-data nextcloud php occ config:list
sudo docker exec -u www-data nextcloud php occ app:list
sudo docker exec -u www-data nextcloud php occ db:add-missing-indices
sudo docker exec -u www-data nextcloud php occ maintenance:repair --include-expensive
```
## Adding a New Service
@@ -194,15 +330,91 @@ or low cost, and Restic handles encryption + deduplication automatically. A cron
### Recommended Alerts
Set these up in Grafana Cloud UI (**Alerting** -> **Alert rules**):
Set these up in Grafana Cloud UI (**Alerting** -> **Alert rules** -> **New alert rule**). Choose **Grafana-managed rule**
and select the appropriate data source (Prometheus or Loki).
| Alert | Condition | Severity |
|----------------------|-----------------------------------------------------------------------|----------|
| Disk usage high | `node_filesystem_avail_bytes` / `node_filesystem_size_bytes` < 0.2 | Critical |
| Container restarting | Container restart count > 3 in 10 min | Warning |
| High memory usage | `node_memory_MemAvailable_bytes` / `node_memory_MemTotal_bytes` < 0.1 | Warning |
| High CPU usage | `node_cpu_seconds_total` idle < 10% sustained 5 min | Warning |
| Nextcloud cron stale | No log line from `nextcloud-cron` in 15 min | Warning |
| Alert | Condition | Severity |
|----------------------|--------------------------------------|----------|
| Disk usage high | Available disk < 20% | Critical |
| Container restarting | Restart count > 3 in 10 min | Warning |
| High memory usage | Available memory < 10% | Warning |
| High CPU usage | CPU usage > 90% sustained 5 min | Warning |
| Nextcloud cron stale | No cron log lines in 15 min | Warning |
#### Disk usage high
Fires when any filesystem drops below 20% free space.
- **Data source:** Prometheus
- **Query (A):**
```promql
node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} * 100
```
- **Expression (B):** Threshold — `A IS BELOW 20`
- **Evaluate every:** `1m`
- **Pending period (For):** `5m`
- **Labels:** `severity: critical`
#### Container restarting
Fires when any container restarts more than 3 times in 10 minutes, indicating a crash loop.
Detects both in-place restarts (`docker restart`) and ID-changing restarts (`docker compose down/up`).
Requires cAdvisor (included in the monitoring stack).
- **Data source:** Prometheus
- **Query (A):**
```promql
sum by (name) (changes(container_start_time_seconds{name!=""}[10m]))
+
count by (name) (count_over_time(container_start_time_seconds{name!=""}[10m])) - 1
```
- **Expression (B):** Threshold — `A IS ABOVE 3`
- **Evaluate every:** `1m`
- **Pending period (For):** `0s`
- **Labels:** `severity: warning`
#### High memory usage
Fires when available memory drops below 10% of total.
- **Data source:** Prometheus
- **Query (A):**
```promql
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100
```
- **Expression (B):** Threshold — `A IS BELOW 10`
- **Evaluate every:** `1m`
- **Pending period (For):** `5m`
- **Labels:** `severity: warning`
#### High CPU usage
Fires when average CPU usage exceeds 90% for 5 minutes.
- **Data source:** Prometheus
- **Query (A):**
```promql
avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
```
- **Expression (B):** Threshold — `A IS BELOW 10`
- **Evaluate every:** `1m`
- **Pending period (For):** `5m`
- **Labels:** `severity: warning`
#### Nextcloud cron stale
Fires when no log output from the `nextcloud-cron` container appears for 15 minutes, indicating background jobs have stopped.
- **Data source:** Loki
- **Query (A):**
```logql
count_over_time({container="/nextcloud-cron"}[15m])
```
- **Expression (B):** Threshold — `A IS BELOW 1`
- **Alert condition:** also trigger on **No Data**
- **Evaluate every:** `5m`
- **Pending period (For):** `0s`
- **Labels:** `severity: warning`
### Recommended Dashboards

View File

@@ -9,12 +9,14 @@
}
nextcloud.t-gstone.de {
reverse_proxy nextcloud:80
reverse_proxy nextcloud-nginx:80
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
header Referrer-Policy "no-referrer"
header X-Content-Type-Options "nosniff"
header X-Frame-Options "SAMEORIGIN"
header X-Permitted-Cross-Domain-Policies "none"
header X-Robots-Tag "noindex, nofollow"
request_body {
max_size 10G

View File

@@ -3,6 +3,8 @@ services:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
depends_on:
- alloy
ports:
- "80:80"
- "443:443"

View File

@@ -3,6 +3,8 @@ services:
image: gitea/gitea:1.25.5-rootless
container_name: gitea
restart: unless-stopped
depends_on:
- alloy
env_file: .env
volumes:
- ${DATA_ROOT}/gitea/data:/var/lib/gitea

View File

@@ -54,6 +54,18 @@ prometheus.scrape "node" {
scrape_interval = "60s"
}
// ============================================================
// cAdvisor container metrics -> Grafana Cloud Prometheus
// ============================================================
prometheus.scrape "cadvisor" {
targets = [{"__address__" = "cadvisor:8080"}]
forward_to = [prometheus.remote_write.grafana_cloud.receiver]
scrape_interval = "60s"
metrics_path = "/metrics"
}
prometheus.remote_write "grafana_cloud" {
endpoint {
url = env("GRAFANA_CLOUD_PROMETHEUS_URL")

View File

@@ -16,7 +16,7 @@ services:
- EXEC=0
- IMAGES=0
- INFO=0
- NETWORKS=0
- NETWORKS=1
- NODES=0
- PLUGINS=0
- SERVICES=0
@@ -33,6 +33,29 @@ services:
max-size: "10m"
max-file: "3"
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.54.1
container_name: cadvisor
restart: unless-stopped
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /run/containerd/containerd.sock:/run/containerd/containerd.sock:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
command:
- --docker_only=true
- --housekeeping_interval=30s
- --containerd=/run/containerd/containerd.sock
- --disable_metrics=cpu_topology,disk,diskIO,hugetlb,memory_numa,network,oom_event,percpu,perf_event,process,referenced_memory,resctrl,sched,tcp,udp
networks:
- monitoring
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
alloy:
image: grafana/alloy:v1.14.1
container_name: alloy

View File

@@ -1,6 +1,6 @@
services:
nextcloud:
image: nextcloud:29-apache
image: nextcloud:33-fpm
container_name: nextcloud
restart: unless-stopped
depends_on:
@@ -13,9 +13,37 @@ services:
- POSTGRES_HOST=postgres
- REDIS_HOST=redis
- REDIS_HOST_PASSWORD=${REDIS_PASSWORD}
- TRUSTED_PROXIES=172.18.0.0/16
volumes:
- ${DATA_ROOT}/nextcloud/html:/var/www/html
- ${DATA_ROOT}/nextcloud/data:/var/www/html/data
- ./hooks/post-installation.sh:/docker-entrypoint-hooks.d/post-installation/post-installation.sh:ro
- ./hooks/post-upgrade.sh:/docker-entrypoint-hooks.d/post-upgrade/post-upgrade.sh:ro
- ./fpm-tuning.conf:/usr/local/etc/php-fpm.d/zz-tuning.conf:ro
- ./php-tuning.ini:/usr/local/etc/php/conf.d/zz-tuning.ini:ro
networks:
- nextcloud-internal
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
nginx:
image: nginx:alpine
container_name: nextcloud-nginx
restart: unless-stopped
depends_on:
- nextcloud
volumes:
- ${DATA_ROOT}/nextcloud/html:/var/www/html:ro
- ${DATA_ROOT}/nextcloud/data:/var/www/html/data:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost/status.php || exit 1"]
interval: 30s
timeout: 10s
retries: 3
networks:
- proxy
- nextcloud-internal
@@ -24,16 +52,13 @@ services:
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/status.php"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
image: postgres:17-alpine
container_name: nextcloud-postgres
restart: unless-stopped
depends_on:
- alloy
env_file: .env
volumes:
- ${DATA_ROOT}/nextcloud/db:/var/lib/postgresql/data
@@ -51,9 +76,11 @@ services:
max-file: "3"
redis:
image: redis:7-alpine
image: redis:8-alpine
container_name: nextcloud-redis
restart: unless-stopped
depends_on:
- alloy
command: redis-server --requirepass ${REDIS_PASSWORD}
env_file: .env
networks:
@@ -65,7 +92,7 @@ services:
max-file: "3"
cron:
image: nextcloud:29-apache
image: nextcloud:33-fpm
container_name: nextcloud-cron
restart: unless-stopped
depends_on:

View File

@@ -0,0 +1,7 @@
[www]
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500

View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -eu
echo "==> Post-installation: setting maintenance window start to 01:00 UTC..."
php occ config:system:set maintenance_window_start --type=integer --value=1
echo "==> Post-installation: setting default phone region to DE..."
php occ config:system:set default_phone_region --value="DE"
echo "==> Post-installation: adding missing DB indices..."
php occ db:add-missing-indices
echo "==> Post-installation: running MIME type migrations..."
php occ maintenance:repair --include-expensive
echo "==> Post-installation: configuring Redis caching and file locking..."
php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
php occ config:system:set memcache.distributed --value="\\OC\\Memcache\\Redis"
php occ config:system:set memcache.locking --value="\\OC\\Memcache\\Redis"
echo "==> Post-installation: done."

10
nextcloud/hooks/post-upgrade.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
set -eu
echo "==> Post-upgrade: adding missing DB indices..."
php occ db:add-missing-indices
echo "==> Post-upgrade: running MIME type migrations..."
php occ maintenance:repair --include-expensive
echo "==> Post-upgrade: done."

87
nextcloud/nginx.conf Normal file
View File

@@ -0,0 +1,87 @@
upstream php-handler {
server nextcloud:9000;
}
map $uri $nonce_uri {
default "";
}
map $arg_v $asset_immutable {
"" "";
default ", immutable";
}
server {
listen 80;
server_name _;
include mime.types;
types {
application/javascript mjs;
}
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
client_max_body_size 10G;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
root /var/www/html;
index index.php index.html /index.php$request_uri;
# Redirect well-known URLs
location ^~ /.well-known {
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
return 301 /index.php$request_uri;
}
# Deny access to internal paths
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
# PHP handling (must be before static file locations so that internal
# redirects like /index.php/apps/theming/theme/dark.css match here
# instead of cycling back into the static file try_files)
location ~ \.php(?:$|/) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_hide_header X-Powered-By;
fastcgi_request_buffering off;
fastcgi_max_temp_file_size 0;
}
# Serve static files directly, fall through to PHP for dynamic assets (e.g. theming)
location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac|mp4|webm)$ {
try_files $uri /index.php$request_uri;
add_header Cache-Control "public, max-age=15778463$asset_immutable";
access_log off;
}
location ~ \.woff2?$ {
try_files $uri /index.php$request_uri;
expires 7d;
access_log off;
}
# Default handler — route everything else through PHP front controller
location / {
rewrite ^ /index.php$request_uri last;
}
}

10
nextcloud/php-tuning.ini Normal file
View File

@@ -0,0 +1,10 @@
; OPcache tuning for Nextcloud
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.save_comments=1
opcache.enable_file_override=1
; APCu local cache
apc.shm_size=64M
apc.enable_cli=1

View File

@@ -61,6 +61,7 @@ echo "==> Creating data directories under $DATA_ROOT..."
mkdir -p "$DATA_ROOT"/{caddy/data,caddy/config}
mkdir -p "$DATA_ROOT"/{nextcloud/html,nextcloud/data,nextcloud/db}
mkdir -p "$DATA_ROOT"/{gitea/data,gitea/config}
chown -R 1000:1000 "$DATA_ROOT"/gitea
mkdir -p /opt/backups
# ------------------------------------------------------------------