diff --git a/cmd/saml-server/main.go b/cmd/saml-server/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..bddc3355baa9c2af40b6f0bcdb47189e0ddf0e5f
--- /dev/null
+++ b/cmd/saml-server/main.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"os/signal"
+	"strings"
+	"syscall"
+	"time"
+
+	"git.autistici.org/id/go-sso/saml"
+
+	"gopkg.in/yaml.v2"
+)
+
+var (
+	addr       = flag.String("addr", ":5004", "address to listen on")
+	configFile = flag.String("config", "/etc/sso/saml.yml", "`path` of config file")
+)
+
+func loadConfig() (*saml.Config, error) {
+	// Read YAML config.
+	data, err := ioutil.ReadFile(*configFile)
+	if err != nil {
+		return nil, err
+	}
+	var config saml.Config
+	if err := yaml.Unmarshal(data, &config); err != nil {
+		return nil, err
+	}
+	return &config, nil
+}
+
+// Set defaults for command-line flags using variables from the environment.
+func setFlagDefaultsFromEnv() {
+	flag.VisitAll(func(f *flag.Flag) {
+		envVar := "SAML_" + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
+		if value := os.Getenv(envVar); value != "" {
+			f.DefValue = value
+			f.Value.Set(value)
+		}
+	})
+}
+
+func main() {
+	setFlagDefaultsFromEnv()
+	flag.Parse()
+
+	config, err := loadConfig()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	s, err := saml.NewSAMLIDP(config)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	srv := &http.Server{
+		Addr:    *addr,
+		Handler: s,
+	}
+
+	sigCh := make(chan os.Signal, 1)
+	go func() {
+		<-sigCh
+		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		defer cancel()
+		_ = srv.Shutdown(ctx)
+		_ = srv.Close()
+	}()
+	signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
+
+	if err := srv.ListenAndServe(); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/cmd/saml-server/saml-server b/cmd/saml-server/saml-server
new file mode 100755
index 0000000000000000000000000000000000000000..f2f0075ac6d0e7456880a8c9cb733d408d0fbe2d
Binary files /dev/null and b/cmd/saml-server/saml-server differ
diff --git a/debian/control b/debian/control
index d011af6a3c72a2943b684b9c52dd9653dcf4ec36..96d4fa68d21b82d1145e14a1fc8fce4db0a123be 100644
--- a/debian/control
+++ b/debian/control
@@ -16,3 +16,9 @@ Architecture: any
 Depends: ${shlibs:Depends}, ${misc:Depends}
 Description: Single-Sign-On HTTP proxy.
  Single-Sign-On HTTP proxy.
+
+Package: saml-server
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: SAML Single-Sign-On bridge
+ SAML Single-Sign-On bridge.
diff --git a/debian/saml-server.default b/debian/saml-server.default
new file mode 100644
index 0000000000000000000000000000000000000000..8536d1352c917777d87a51ff0db887ffbec3f839
--- /dev/null
+++ b/debian/saml-server.default
@@ -0,0 +1 @@
+ADDR=:5004
diff --git a/debian/saml-server.install b/debian/saml-server.install
new file mode 100644
index 0000000000000000000000000000000000000000..87af1cd06b395c016b36b11ba515c57f88c7d402
--- /dev/null
+++ b/debian/saml-server.install
@@ -0,0 +1 @@
+usr/bin/saml-server
diff --git a/debian/saml-server.postinst b/debian/saml-server.postinst
new file mode 100755
index 0000000000000000000000000000000000000000..73768438ac8522fd4dbf496e61d47f5f3dd66ae6
--- /dev/null
+++ b/debian/saml-server.postinst
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -e
+
+case "$1" in
+configure)
+    addgroup --system --quiet sso-proxy
+    adduser --system --no-create-home --home /run/sso-proxy \
+      --disabled-password --disabled-login \
+      --quiet --ingroup sso-proxy sso-proxy
+    ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/saml-server.service b/debian/saml-server.service
new file mode 100644
index 0000000000000000000000000000000000000000..25b2a91be3a19b1036cf9aeea8abb0891747d7ad
--- /dev/null
+++ b/debian/saml-server.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=SAML SSO Bridge 
+
+[Service]
+User=saml-server
+Group=saml-server
+EnvironmentFile=-/etc/default/saml-server
+ExecStart=/usr/bin/saml-server --addr $ADDR
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/saml/saml.go b/saml/saml.go
index 40713e8f9d21da7fe056fb8623fa96480a0deb1d..6d5fe2d42786075891b57b2228a7483ad928eb9e 100644
--- a/saml/saml.go
+++ b/saml/saml.go
@@ -5,6 +5,7 @@ import (
 	"crypto/tls"
 	"encoding/base64"
 	"encoding/hex"
+	"encoding/xml"
 	"errors"
 	"fmt"
 	"io/ioutil"
@@ -22,13 +23,6 @@ import (
 	"git.autistici.org/id/go-sso/httpsso"
 )
 
-type ServiceProviderConfig struct {
-}
-
-func (p *ServiceProviderConfig) toEntity() *saml.EntityDescriptor {
-	return nil
-}
-
 type Config struct {
 	BaseURL string `yaml:"base_url"`
 
@@ -46,7 +40,8 @@ type Config struct {
 	SSODomain         string `yaml:"sso_domain"`
 
 	// Service provider config.
-	ServiceProviders map[string]*ServiceProviderConfig `yaml:"service_providers"`
+	ServiceProviders       []string `yaml:"service_providers"`
+	parsedServiceProviders map[string]*saml.EntityDescriptor
 }
 
 // Sanity checks for the configuration.
@@ -74,12 +69,28 @@ func (c *Config) check() error {
 	return nil
 }
 
+func (c *Config) loadServiceProviders() error {
+	c.parsedServiceProviders = make(map[string]*saml.EntityDescriptor)
+	for _, path := range c.ServiceProviders {
+		data, err := ioutil.ReadFile(path)
+		if err != nil {
+			return err
+		}
+		var ent saml.EntityDescriptor
+		if err := xml.Unmarshal(data, &ent); err != nil {
+			return err
+		}
+		c.parsedServiceProviders[ent.EntityID] = &ent
+	}
+	return nil
+}
+
 func (c *Config) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) {
-	srv, ok := c.ServiceProviders[serviceProviderID]
+	srv, ok := c.parsedServiceProviders[serviceProviderID]
 	if !ok {
 		return nil, os.ErrNotExist
 	}
-	return srv.toEntity(), nil
+	return srv, nil
 }
 
 // Read users from a YAML-encoded file, in a format surprisingly
@@ -142,6 +153,9 @@ func NewSAMLIDP(config *Config) (http.Handler, error) {
 	if err := config.check(); err != nil {
 		return nil, err
 	}
+	if err := config.loadServiceProviders(); err != nil {
+		return nil, err
+	}
 
 	cert, err := tls.LoadX509KeyPair(config.CertificateFile, config.PrivateKeyFile)
 	if err != nil {