diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9860702e0..6ec6a7c91 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,15 +6,30 @@ labels: bug assignees: '' --- + **Checklist** - Have you pulled and found the error with `jc21/nginx-proxy-manager:latest` docker image? + - Yes / No - Are you sure you're not using someone else's docker image? -- If having problems with Lets Encrypt, have you made absolutely sure your site is accessible from outside of your network? + - Yes / No +- Have you searched for similar issues (both open and closed)? + - Yes / No **Describe the bug** -- A clear and concise description of what the bug is. -- What version of Nginx Proxy Manager is reported on the login page? + + + +**Nginx Proxy Manager Version** + + **To Reproduce** Steps to reproduce the behavior: @@ -23,14 +38,18 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error + **Expected behavior** -A clear and concise description of what you expected to happen. + + **Screenshots** -If applicable, add screenshots to help explain your problem. + + **Operating System** -- Please specify if using a Rpi, Mac, orchestration tool or any other setups that might affect the reproduction of this error. + + **Additional context** -Add any other context about the problem here, docker version, browser version if applicable to the problem. Too much info is better than too little. + diff --git a/.github/ISSUE_TEMPLATE/dns_challenge_request.md b/.github/ISSUE_TEMPLATE/dns_challenge_request.md new file mode 100644 index 000000000..0a00f00f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/dns_challenge_request.md @@ -0,0 +1,18 @@ +--- +name: DNS challenge provider request +about: Suggest a new provider to be available for a certificate DNS challenge +title: '' +labels: dns provider request +assignees: '' + +--- + +**What provider would you like to see added to NPM?** + + + +**Have you checked if a certbot plugin exists?** + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491ef..cf5b0f772 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,26 @@ assignees: '' --- + + **Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + + **Describe the solution you'd like** -A clear and concise description of what you want to happen. + + **Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. + + **Additional context** -Add any other context or screenshots about the feature request here. + diff --git a/.github/ISSUE_TEMPLATE/product_support.md b/.github/ISSUE_TEMPLATE/product_support.md deleted file mode 100644 index 449d4697b..000000000 --- a/.github/ISSUE_TEMPLATE/product_support.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Product Support -about: Need help configuring the software? -title: '' -labels: product-support -assignees: '' - ---- - -**Checklist** -- Please read the [setup instructions](https://nginxproxymanager.com/setup/) -- Please read the [FAQ](https://nginxproxymanager.com/faq/) - -**What is troubling you?** - -_Clear and concise description of what you're trying to do and what isn't working for you_ diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..f859b1278 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,21 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-label: 'stale' + stale-pr-label: 'stale' + stale-issue-message: 'Issue is now considered stale. If you want to keep it open, please comment :+1:' + stale-pr-message: 'PR is now considered stale. If you want to keep it open, please comment :+1:' + close-issue-message: 'Issue was closed due to inactivity.' + close-pr-message: 'PR was closed due to inactivity.' + days-before-stale: 182 + days-before-close: 365 + operations-per-run: 50 diff --git a/.gitignore b/.gitignore index 08462849d..fbb8167e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ ._* .vscode certbot-help.txt +test/node_modules +*/node_modules +docker/dev/dnsrouter-config.json.tmp +docker/dev/resolv.conf diff --git a/.jenkins/config-mysql.json b/.jenkins/config-mysql.json deleted file mode 100644 index 19ad2237f..000000000 --- a/.jenkins/config-mysql.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "database": { - "engine": "mysql", - "host": "db", - "name": "npm", - "user": "npm", - "password": "npm", - "port": 3306 - } -} \ No newline at end of file diff --git a/.jenkins/config-sqlite.json b/.jenkins/config-sqlite.json deleted file mode 100644 index 97d688d21..000000000 --- a/.jenkins/config-sqlite.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "database": { - "engine": "knex-native", - "knex": { - "client": "sqlite3", - "connection": { - "filename": "/data/database.sqlite" - } - } - } -} diff --git a/.version b/.version index e70b4523a..e4643748f 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.6.0 +2.12.6 diff --git a/Jenkinsfile b/Jenkinsfile index 74dc0a1e0..af913c2e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,3 +1,9 @@ +import groovy.transform.Field + +@Field +def shOutput = "" +def buildxPushTags = "" + pipeline { agent { label 'docker-multiarch' @@ -8,14 +14,12 @@ pipeline { ansiColor('xterm') } environment { - IMAGE = "nginx-proxy-manager" + IMAGE = 'nginx-proxy-manager' BUILD_VERSION = getVersion() - MAJOR_VERSION = "2" - BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('/', '-')}" - COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}" - COMPOSE_FILE = 'docker/docker-compose.ci.yml' + MAJOR_VERSION = '2' + BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('\\\\', '-').replaceAll('/', '-').replaceAll('\\.', '-')}" + BUILDX_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}" COMPOSE_INTERACTIVE_NO_CLI = 1 - BUILDX_NAME = "${COMPOSE_PROJECT_NAME}" } stages { stage('Environment') { @@ -26,7 +30,7 @@ pipeline { } steps { script { - env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest" + buildxPushTags = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest" } } } @@ -39,7 +43,7 @@ pipeline { steps { script { // Defaults to the Branch name, which is applies to all branches AND pr's - env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}" + buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}" } } } @@ -52,107 +56,153 @@ pipeline { sh 'sed -i -E "s/(version-)[0-9]+\\.[0-9]+\\.[0-9]+(-green)/\\1${BUILD_VERSION}\\2/" README.md' } } + stage('Docker Login') { + steps { + withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { + sh 'docker login -u "${duser}" -p "${dpass}"' + } + } + } } } - stage('Frontend') { - steps { - sh './scripts/frontend-build' + stage('Builds') { + parallel { + stage('Project') { + steps { + script { + // Frontend and Backend + def shStatusCode = sh(label: 'Checking and Building', returnStatus: true, script: ''' + set -e + ./scripts/ci/frontend-build > ${WORKSPACE}/tmp-sh-build 2>&1 + ./scripts/ci/test-and-build > ${WORKSPACE}/tmp-sh-build 2>&1 + ''') + shOutput = readFile "${env.WORKSPACE}/tmp-sh-build" + if (shStatusCode != 0) { + error "${shOutput}" + } + } + } + post { + always { + sh 'rm -f ${WORKSPACE}/tmp-sh-build' + } + failure { + npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true) + } + } + } + stage('Docs') { + steps { + dir(path: 'docs') { + sh 'yarn install' + sh 'yarn build' + } + } + } } } - stage('Backend') { - steps { - echo 'Checking Syntax ...' - // See: https://github.com/yarnpkg/yarn/issues/3254 - sh '''docker run --rm \\ - -v "$(pwd)/backend:/app" \\ - -v "$(pwd)/global:/app/global" \\ - -w /app \\ - node:latest \\ - sh -c "yarn install && yarn eslint . && rm -rf node_modules" - ''' - - echo 'Docker Build ...' - sh '''docker build --pull --no-cache --squash --compress \\ - -t "${IMAGE}:ci-${BUILD_NUMBER}" \\ - -f docker/Dockerfile \\ - --build-arg TARGETPLATFORM=linux/amd64 \\ - --build-arg BUILDPLATFORM=linux/amd64 \\ - --build-arg BUILD_VERSION="${BUILD_VERSION}" \\ - --build-arg BUILD_COMMIT="${BUILD_COMMIT}" \\ - --build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \\ - . - ''' + stage('Test Sqlite') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml' + } + when { + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } } - } - stage('Integration Tests Sqlite') { steps { - // Bring up a stack - sh 'docker-compose up -d fullstack-sqlite' - sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120' - - // Run tests - sh 'rm -rf test/results' - sh 'docker-compose up cypress-sqlite' - // Get results - sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/' + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' } post { always { // Dumps to analyze later - sh 'mkdir -p debug' - sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz' - sh 'docker-compose logs db | gzip > debug/docker_db.log.gz' - // Cypress videos and screenshot artifacts + sh 'mkdir -p debug/sqlite' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + } + unstable { dir(path: 'test/results') { - archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml' + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } - junit 'test/results/junit/*' } } } - stage('Integration Tests Mysql') { + stage('Test Mysql') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml' + } + when { + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } + } steps { - // Bring up a stack - sh 'docker-compose up -d fullstack-mysql' - sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120' - - // Run tests - sh 'rm -rf test/results' - sh 'docker-compose up cypress-mysql' - // Get results - sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/' + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' } post { always { // Dumps to analyze later - sh 'mkdir -p debug' - sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz' - sh 'docker-compose logs db | gzip > debug/docker_db.log.gz' - // Cypress videos and screenshot artifacts + sh 'mkdir -p debug/mysql' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + } + unstable { dir(path: 'test/results') { - archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml' + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } - junit 'test/results/junit/*' } } } - stage('Docs') { + stage('Test Postgres') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml' + } when { not { equals expected: 'UNSTABLE', actual: currentBuild.result } } steps { - dir(path: 'docs') { - sh 'yarn install' - sh 'yarn build' - } + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' + } + post { + always { + // Dumps to analyze later + sh 'mkdir -p debug/postgres' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' - dir(path: 'docs/.vuepress/dist') { - sh 'tar -czf ../../docs.tgz *' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + } + unstable { + dir(path: 'test/results') { + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') + } } - - archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false) } } stage('MultiArch Build') { @@ -162,81 +212,64 @@ pipeline { } } steps { - withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { - // Docker Login - sh "docker login -u '${duser}' -p '${dpass}'" - // Buildx with push from cache - sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}" - } + sh "./scripts/buildx --push ${buildxPushTags}" } } - stage('Docs Deploy') { - when { - allOf { - branch 'master' - not { - equals expected: 'UNSTABLE', actual: currentBuild.result + stage('Docs / Comment') { + parallel { + stage('Docs Job') { + when { + allOf { + branch pattern: "^(develop|master)\$", comparator: "REGEXP" + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } + } + } + steps { + build wait: false, job: 'nginx-proxy-manager-docs', parameters: [string(name: 'docs_branch', value: "$BRANCH_NAME")] } } - } - steps { - withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'npm-s3-docs', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { - sh """docker run --rm \\ - --name \${COMPOSE_PROJECT_NAME}-docs-upload \\ - -e S3_BUCKET=jc21-npm-site \\ - -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \\ - -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\ - -v \$(pwd):/app \\ - -w /app \\ - jc21/ci-tools \\ - scripts/docs-upload /app/docs/.vuepress/dist/ - """ + stage('PR Comment') { + when { + allOf { + changeRequest() + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } + } + } + steps { + script { + npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev): +``` +nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER} +``` - sh """docker run --rm \\ - --name \${COMPOSE_PROJECT_NAME}-docs-invalidate \\ - -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \\ - -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\ - jc21/ci-tools \\ - aws cloudfront create-invalidation --distribution-id EN1G6DEWZUTDT --paths '/*' - """ - } - } - } - stage('PR Comment') { - when { - allOf { - changeRequest() - not { - equals expected: 'UNSTABLE', actual: currentBuild.result +> [!NOTE] +> Ensure you backup your NPM instance before testing this image! Especially if there are database changes. +> This is a different docker image namespace than the official image. + +> [!WARNING] +> Changes and additions to DNS Providers require verification by at least 2 members of the community! +""", true) + } } } } - steps { - script { - def comment = pullRequest.comment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`") - } - } } } post { always { - sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30' sh 'echo Reverting ownership' - sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} chown -R $(id -u):$(id -g) /data' - } - success { - juxtapose event: 'success' - sh 'figlet "SUCCESS"' + sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data' + printResult(true) } failure { - archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true) - juxtapose event: 'failure' - sh 'figlet "FAILURE"' + archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) } unstable { - archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true) - juxtapose event: 'unstable' - sh 'figlet "UNSTABLE"' + archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) } } } diff --git a/README.md b/README.md index b94dbcd80..2116a55ae 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,25 @@
This project comes as a pre-built docker image that enables you to easily forward to your websites running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt. -- [Quick Setup](https://nginxproxymanager.com#quick-setup) +- [Quick Setup](#quick-setup) - [Full Setup](https://nginxproxymanager.com/setup/) - [Screenshots](https://nginxproxymanager.com/screenshots/) ## Project Goal -I created this project to fill a personal need to provide users with a easy way to accomplish reverse +I created this project to fill a personal need to provide users with an easy way to accomplish reverse proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed. While there might be advanced options they are optional and the project should be as simple as possible so that the barrier for entry here is low. @@ -52,160 +46,75 @@ I won't go in to too much detail here but here are the basics for someone new to 3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns) 4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services +## Quick Setup + +1. Install Docker and Docker-Compose + +- [Docker Install documentation](https://docs.docker.com/install/) +- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/) + +2. Create a docker-compose.yml file similar to this: + +```yml +services: + app: + image: 'docker.io/jc21/nginx-proxy-manager:latest' + restart: unless-stopped + ports: + - '80:80' + - '81:81' + - '443:443' + volumes: + - ./data:/data + - ./letsencrypt:/etc/letsencrypt +``` + +This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more. + +3. Bring up your stack by running + +```bash +docker-compose up -d + +# If using docker-compose-plugin +docker compose up -d + +``` + +4. Log in to the Admin UI + +When your docker container is running, connect to it on port `81` for the admin interface. +Sometimes this can take a little bit because of the entropy of keys. + +[http://127.0.0.1:81](http://127.0.0.1:81) + +Default Admin User: +``` +Email: admin@example.com +Password: changeme +``` + +Immediately after logging in with this default user you will be asked to modify your details and change your password. + + +## Contributing + +All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch. + +CI is used in this project. All PR's must pass before being considered. After passing, +docker builds for PR's are available on dockerhub for manual verifications. + +Documentation within the `develop` branch is available for preview at +[https://develop.nginxproxymanager.com](https://develop.nginxproxymanager.com) + + +### Contributors + +Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors). + + +## Getting Support -## Contributors - -Special thanks to the following contributors: - - - -
-
- Sebastian Valle - - |
-
-
- Kyle Klaus - - |
-
-
- ƬHE ЯAW - - |
-
-
- Spencer - - |
-
-
- Xantios Krugor - - |
-
-
- David Panesso - - |
-
-
- IronTooch - - |
-
-
- Damiano - - |
-
-
- Russ - - |
-
-
- Marcelo Castagna - - |
-
-
- Steven Harris - - |
-
-
- Jocelyn Le Sage - - |
-
-
- Carl Mercier - - |
-
-
- Paul Mansfield - - |
-
-
- OhHeyAlan - - |
-
-
- Carl Sutton - - |
-
-
- Gergő Törcsvári - - |
-
-
- vrenjith - - |
-
-
- David Rivera - - |
-
-
- Jaap-Jan de Wit - - |
-
-
- James Morgan - - |
-
-
- chaptergy - - |
-
-
- Philip Mooney - - |
-
- Expose web services on your network · - Free SSL with Let's Encrypt · - Designed with security in mind · - Perfect for home networks -
-Expose your private network Web services and get connected anywhere.
-Based on Tabler, the interface is a pleasure to use. Configuring a server has never been so fun.
-Built in Let’s Encrypt support allows you to secure your Web services at no cost to you. The certificates even renew themselves!
-Built as a Docker Image, Nginx Proxy Manager only requires a database.
-Configure other users to either view or manage their own hosts. Full access permissions are available.
-+ Basic Authorization via + + Nginx HTTP Basic Authentication + +
+ IP Address Whitelist/Blacklist via + + Nginx HTTP Access + +
allow
and deny
directives will be applied in the order they are defined.