One place for hosting & domains

      Source

      An Overview of Open Source Data Visualization Tools


      Updated by Linode Contributed by Mihalis Tsoukalos

      Creating graphic visualizations for a data set is a powerful way to derive meaning from vast amounts of information. It provides a way to extract meaningful relationships between different aspects of your data depending on how the data is mapped and which graphic representations are chosen. Data visualization is a common practice in many sectors, including various scientific disciplines, business settings, the government sector, and education.

      There are many open source tools available to create sophisticated data visualizations for complex data sets. This guide will provide an introductory exploration of data analysis and 2D graphics rendering packages that can be used with R, Python, and JavaScript to generate data visualizations.

      In this guide you will complete the following steps:

      Before You Begin

      Ensure you have the following programs and packages installed on your computer:

      1. The R programming language
      2. The RStudio Desktop application
      3. Python 3.7.0 or higher
        1. pandas Python package
        2. Matplotlib Python package
        3. wordcloud Python package
      4. The Golang programming language

      Assumptions

      This guide assumes you have some basic familiarity with the following concepts and skills:

      1. Basic programming principles and data structures
      2. Are able to read code written in Go, Python, HTML, CSS, and JavaScript

      Create Your Data Sets

      In this section, you will create a data set using the contents of your Bash history file and optionally, your Zsh history file. You will then create a third data set using a Perl script that will extract information from the first two data sets. In the Create Visualizations for your Data section of the guide, you will use these various data sets to create corresponding visualizations.

      Data Set 1 – Bash History File

      A Bash history file stores all commands executed in your command line interpreter. View your 10 most recently executed commands with the following command:

      head ~/.bash_history
      

      Your output should resemble the following:

        
      git commit -a -m "Fixed Constants links"
      git push
      git diff
      docker images
      brew update; brew upgrade; brew cleanup
      git commit -a -m "Cleanup v2 and v3"
      git push
      git commit -a -m "Added examples section"
      git push
      cat ~/.lenses/lenses-cli.yml
      
      

      Create a new directory named data-setsto store your data and copy your Bash history file to the directory:

      mkdir data-sets && cp ~/.bash_history data-sets/data-1
      

      Data Set 2 – Zsh History File

      If you are using the Zsh shell interpreter, you can use its history file as a second data set. Zsh’s history file format includes data that you will need to exclude from your data set. Use AWK to clean up your Zsh history file and save the output to a new file in the data-sets directory:

      awk -F ";" '{$1=""; print $0}' ~/.zsh_history | sed -e "s/^[ t]*//" -e "/^$/d" > data-sets/data-2
      

      Data Set 3 – Perl Script

      To create your third data set, you will use a Perl script that categorizes the contents of your data set files. The categorization is based on the word count for each line of text in your data files or, in other words, the length of each command stored in your Bash and Zsh history files. The script creates 5 categories of command lengths; 1 – 2 words, 3 – 5 words, 6 – 10 words, 11 – 15 words, and 16 or more words.

      1. Create a file named command_length.pl in your home directory with the following content:

        ~/command_length.pl
         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
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        
        #!/usr/bin/perl -w
        
        use strict;
        
        my $directory = "";
        my $filename = "";
        
        my $CAT1 = 0;
        my $CAT2 = 0;
        my $CAT3 = 0;
        my $CAT4 = 0;
        my $CAT5 = 0;
        
        if ( @ARGV != 1 ) {
           die <<Thanatos
              usage info:
                 Please use exactly 1 argument!
        Thanatos
        }
        
        ($directory) = @ARGV;
        opendir(BIN, $directory)
            || die "Error opening directory $directory: $!n";
        
        while (defined ($filename = readdir BIN) ) {
            # The following command does not process . and ..
            next if( $filename =~ /^..?$/ );
            process_file($directory."/".$filename);
        }
        
        print "Category1tt$CAT1n";
        print "Category2tt$CAT2n";
        print "Category3tt$CAT3n";
        print "Category4tt$CAT4n";
        print "Category5tt$CAT5n";
        exit 0;
        
        sub process_file {
            my $file = shift;
            my $line = "";
        
            open (HISTORYFILE, "< $file")
                || die "Cannot open $file: $!n";
        
            while (defined($line = <HISTORYFILE>)) {
                chomp $line;
                next if ( ! defined($line) );
                check_category($line);
            }
        }
        
        sub check_category {
            my $command = shift;
            chomp $command;
            my $length = length($command);
        
            if ( $length <= 2 ) { $CAT1 ++; }
            elsif ( $length <= 5 ) { $CAT2 ++; }
            elsif ( $length <= 10 ) { $CAT3 ++; }
            elsif ( $length <= 15 ) { $CAT4 ++; }
            else { $CAT5 ++; }
        }
            
      2. Run the Perl script. The script expects a single argument; the directory that holds all data files that you want to process. The file’s output will be saved in a new file command_categories.txt. The command_categories.txt file will be used later in this guide to create visualizations using R.

        ./command_length.pl data-sets > ~/command_categories.txt
        

        Note

        Your Perl script must be executable in order to run. To add these permissions, execute the following command:

        chmod +x command_length.pl
        

        Open the .command_categories.txt file to view the categorizations created by your Perl script. Your file should resemble the following example:

        command_categories.txt
        1
        2
        3
        4
        5
        6
        7
        
        "Category Name" "Number of Times"
        Category1 5514
        Category2 2381
        Category3 2624
        Category4 2021
        Category5 11055
            

      You now have three sources of data that you can use to explore data visualization tools in the next sections.

      • ~/data-sets/data-1
      • ~/data-stes/data-2
      • ~/command_categories.txt

      Create Visualizations for your Data

      In this section you will use the data sets you created in the previous section to generate visualizations for them.

      Visualize your Data with R and RStudio

      R is a specialized programming language used for statistical computing and graphics. It is especially good for creating high quality graphs, like density plots, line charts, pie charts, and scatter plots. RStudio is an integrated development environment (IDE) for R that includes debugging and plotting tools that make it easy to write, debug, and run R scripts.

      In this section, you will use the command_categories.txt file created in the Data Set 3 – Perl Script section and RStudio to create a pie chart and simple spreadsheet of your data.

      1. Open RStudio Desktop and create a data frame. In R, a data frame is a table similar to a two-dimensional array.

        DATA <- read.table("~/command_categories.txt", header=TRUE)
        
        • This command will read the command_categories.txt file that was created in the Data Set 3 – Perl Script section of the guide and create a data frame from it that is stored in the DATA variable.

        • The header=TRUE argument indicates that the file’s first row contains variable names for column values. This means that Category Name and Number of Times will be used as variable names for the two columns of values in command_categories.txt.

      2. Next, create a pie chart visualization for each column of values using R’s pie() function.

        pie(DATA$Number.of.Times, DATA$Category.Name)
        
        • The function’s first argument, DATA$Number.of.Times and DATA$Category.Name, provides the x-vector numeric values to use when creating the pie chart visualization.

        • The second argument, DATA$Category.Name, provides the labels for each pie slice.

        RStudio will display a pie chart visualization of your data in the Plots and Files window similar to the following:

        Pie Chart

      3. Visualize your data in a spreadsheet style format using R’s View() function.

        View(DATA, "Command Lengths")
        

        RStudio will display a spreadsheet style viewer in a new window pane tab.

        Spreadsheet

      Explore R’s graphics package to discover additional functions you can use to create more complex visualizations for your data with RStudio.

      Create a Word Cloud using Python

      Word clouds depict text data using varying font sizes and colors to visually demonstrate the frequency and relative importance of each word. A common application for word clouds is to visualize tag or keyword relevancy. In this section you will use Python3 and your Bash and Zsh history files to generate a word cloud of all your shell commands. The Python packages listed below are commonly used in programs for data analysis and scientific computing and you will use them to generate your word cloud.

      1. Create a file named create_wordcloud.py in your home directory with the following content:

        ~/create_wordcloud.py
         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
        
        #!/usr/bin/env python3
        
        import pandas as pd
        import matplotlib.pyplot as plt
        import sys
        import os
        from wordcloud import WordCloud, STOPWORDS
        from random import randrange
        
        path = sys.argv[1]
        
        if os.path.exists(path):
            print("Creating word cloud for file: " + path)
        
            data = pd.read_table(path, header = None, names=["CMD"], encoding = "iso-8859-1")
        
            skipWords = []
            skipWords.extend(STOPWORDS)
        
            words = ''.join(data['CMD'])
        
            w = WordCloud(
                    stopwords=set(skipWords),
                    background_color='gray',
                    width=4000,
                    height=2000
                    ).generate(words)
            filename = "word_cloud_" + str(randrange(100)) + ".png"
            print("Your wordcloud's filename is: " + filename)
        
            plt.imshow(w)
            plt.axis("off")
            plt.savefig(filename, dpi=1000)
        
        else:
            print("File" + path +  "does not exist")
            
      2. Run your Python script and pass the path of one of your data set files as an argument. The script will read the contents of the file using panda’s read_table() function and convert it into a data frame with a column name of CMD. It will then use the data in the CMD column to create a concatenated string representation of the data that can be passed to wordcloud to generate a .png wordcloud image.

        ./create_wordcloud.py ~/data-sets/data-1
        

        You should see a similar output from the Python script:

          
        Creating word cloud for file: /Users/username/data-sets/data-2
        Your word cloud's filename is: word_cloud_58.png
            
        
      3. Open the word_cloud_58.png image file to view your word cloud.

        Word Cloud

      You could use a similar process to create a word cloud visualization for any text you want to analyze.

      Visualize Data using D3.js

      D3.js is a JavaScript library that helps you visualize JSON formatted data using HTML, SVG, and CSS. In this section you will us D3.js to create and embed a pie chart visualization into a web page.

      To convert your data set into JSON, you will create a Golang command line utility that generates JSON formatted plain text output. For more complex data sets, you might consider creating a similar command line utility using Golang’s json package.

      1. Create a file named cToJSON.go in your home directory with the following content:

        ./cToJSON.go
         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
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        
        package main
        
        import (
            "bufio"
            "flag"
            "fmt"
            "io"
            "os"
            "regexp"
            "sort"
        )
        
        var DATA = make(map[string]int)
        
        func lineByLine(file string) error {
            var err error
            f, err := os.Open(file)
            if err != nil {
                return err
            }
            defer f.Close()
        
            r := bufio.NewReader(f)
            for {
                line, err := r.ReadString('n')
                if err == io.EOF {
                    break
                } else if err != nil {
                    fmt.Printf("error reading file %s", err)
                    break
                }
        
                r := regexp.MustCompile("[^\s]+")
                words := r.FindAllString(line, -1)
                if len(words) == 0 {
                    continue
                }
        
                if _, ok := DATA[words[0]]; ok {
                    DATA[words[0]]++
                } else {
                    DATA[words[0]] = 1
                }
        
            }
            return nil
        }
        
        func main() {
            flag.Parse()
            if len(flag.Args()) == 0 {
                fmt.Printf("usage: cToJSON <file1> [<file2> ...]n")
                return
            }
        
            for _, file := range flag.Args() {
                err := lineByLine(file)
                if err != nil {
                    fmt.Println(err)
                }
            }
        
            n := map[int][]string{}
            var a []int
            for k, v := range DATA {
                n[v] = append(n[v], k)
            }
        
            for k := range n {
                a = append(a, k)
            }
        
            fmt.Println("[")
            sort.Sort(sort.Reverse(sort.IntSlice(a)))
        
            counter := 0
            for _, k := range a {
                if counter >= 10 {
                    break
                }
        
                for _, s := range n[k] {
                    if counter >= 10 {
                        break
                    }
                    counter++
                    fmt.Printf("{"command":"%s","count":%d},n", s, k)
                }
            }
            fmt.Println("];")
        }
            
        • The utility expects file paths to your Bash and Zsh data sets as arguments.
        • It will then read the files and find the 10 most popular commands and output it as JSON formatted data.
        • Several Golang standard library packages are used in the utility to perform operations liking reading files, using regular expressions, and sorting collections.
      2. Run the command line utility and pass in the paths to each command history data set:

        go run cToJSON.go data-set/data-1 data-set/data-2
        

        Your output should resemble the following:

          
        [
        {"command":"ll","count":1832},
        {"command":"git","count":1567},
        {"command":"cd","count":982},
        {"command":"brew","count":926},
        {"command":"unison","count":916},
        {"command":"gdf","count":478},
        {"command":"ssh","count":474},
        {"command":"rm","count":471},
        {"command":"sync","count":440},
        {"command":"ls","count":421},
        ];
            
        

        You are now ready to create your pie chart visualization and embed it into a web page using D3.js

      3. Create an HTML file named pieChart.html and copy and paste the following content. The DATA variable on line 31 contains the JSON data that was created by the cToJSON.go script in the previous step. Remove the JSON data in the example and replace it with your own JSON data.

        Note

        In this example, your JSON data is hardcoded in pieChart.html for simplicity. Web browser security constraints restrict how a document or script loaded from one origin can interact with a resource from another origin. However, you may consider using the d3-fetch module to fetch your JSON data from a specific URL.
        ~/pieChart.html
          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
         46
         47
         48
         49
         50
         51
         52
         53
         54
         55
         56
         57
         58
         59
         60
         61
         62
         63
         64
         65
         66
         67
         68
         69
         70
         71
         72
         73
         74
         75
         76
         77
         78
         79
         80
         81
         82
         83
         84
         85
         86
         87
         88
         89
         90
         91
         92
         93
         94
         95
         96
         97
         98
         99
        100
        101
        102
        103
        104
        105
        
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <meta charset="utf-8">
            <title>History Visualization</title>
        
            <style type="text/css">
              * { margin: 0; padding: 0; }
        
              #chart {
                background-color: white;
                font: 14px sans-serif;
                margin: 0 auto 50px;
                width: 600px;
                height: 600px;
              }
              #chart .label{
                fill: #404040;
                font-size: 12px;
              }
            </style>
          </head>
        
          <body>
            <div id="chart"></div>
          </body>
        
          <script src="https://d3js.org/d3.v3.min.js"></script>
          <script type="text/javascript">
        
          var DATA = [
              {"command":"ll","count":1832},
              {"command":"git","count":1567},
              {"command":"cd","count":982},
              {"command":"brew","count":926},
              {"command":"unison","count":916},
              {"command":"gdf","count":478},
              {"command":"ssh","count":474},
              {"command":"rm","count":471},
              {"command":"sync","count":440},
              {"command":"ls","count":421}
              ];
        
            var width  = 600;
                height = 600;
                radius = width / 2.5;
        
            var pie = d3.layout.pie()
                        .value(function(d) { return d.count; })
        
            var pieData = pie(DATA);
            var color = d3.scale.category20();
        
            var arc = d3.svg.arc()
                        .innerRadius(0)
                        .outerRadius(radius - 7);
        
            var svg = d3.select("#chart").append("svg")
                        .attr("width", width)
                        .attr("height", height)
                        .append("g")
                        .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
        
            var ticks = svg.selectAll("line")
                           .data(pieData)
                           .enter()
                           .append("line");
        
            ticks.attr("x1", 0)
                 .attr("x2", 0)
                 .attr("y1", -radius+4)
                 .attr("y2", -radius-2)
                 .attr("stroke", "black")
                 .attr("transform", function(d) {
                   return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
                 });
        
            var labels = svg.selectAll("text")
                            .data(pieData)
                            .enter()
                            .append("text");
        
            labels.attr("class", "label")
                  .attr("transform", function(d) {
                     var dist   = radius + 25;
                         angle  = (d.startAngle + d.endAngle) / 2;
                         x      = dist * Math.sin(angle);
                         y      = -dist * Math.cos(angle);
                     return "translate(" + x + "," + y + ")";
                   })
                  .attr("dy", "0.35em")
                  .attr("text-anchor", "middle")
                  .text(function(d){
                    return d.data.command + " (" + d.data.count + ")";
                  });
        
            var path = svg.selectAll("path")
                          .data(pieData)
                          .enter()
                          .append("path")
                          .attr("fill", function(d, i) { return color(i); })
                          .attr("d", arc);
          </script>
        </html>
        
      4. Navigate to your preferred browser and enter the HTML file’s absolute path to view the pie chart. For a macOS user that has stored the HTML file in their home directory, the path would resemble the following: /Users/username/pieChart.html

        JS Pie Chart

      Next Steps

      Now that you are familiar with some data visualization tools and simple techniques, you can begin to explore more sophisticated approaches using the same tools explored in this guide. Here are a few ideas you can consider:

      • Create a new data set by extracting all git related commands from your history files; analyze and visualize them.
      • Automate some of the techniques discussed in this guide using Cron jobs to generate your data sets automatically.
      • Explore the Python for Data Science eBook’s data visualization section for a deeper dive into using pandas.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      How To Install phpMyAdmin From Source on Debian 10


      Introduction

      While many users need the functionality of a database management system like MariaDB, they may not feel comfortable interacting with the system solely from the MariaDB prompt.

      phpMyAdmin was created so that users can interact with MariaDB through a web interface. In this guide, we’ll discuss how to install and secure phpMyAdmin so that you can safely use it to manage your databases on a Debian 10 system.

      Prerequisites

      Before you get started with this guide, you’ll need the following:

      Note: MariaDB is a community-developed fork of MySQL, and although the two programs are closely related, they are not completely interchangeable. While phpMyAdmin was designed specifically for managing MySQL databases and makes reference to MySQL in various dialogue boxes, rest assured that your installation of MariaDB will work correctly with phpMyAdmin.

      Finally, there are important security considerations when using software like phpMyAdmin, since it:

      • Communicates directly with your MariaDB installation
      • Handles authentication using MariaDB credentials
      • Executes and returns results for arbitrary SQL queries

      For these reasons, and because it is a widely-deployed PHP application which is frequently targeted for attack, you should never run phpMyAdmin on remote systems over a plain HTTP connection.

      If you do not have an existing domain configured with an SSL/TLS certificate, you can follow this guide on securing Apache with Let’s Encrypt on Debian 10 to set one up. This will require you to register a domain name, create DNS records for your server, and set up an Apache Virtual Host.

      Once you are finished with these steps, you’re ready to get started with this guide.

      Before installing and configuring phpMyAdmin, the official documentation recommends that you install a few PHP extensions onto your server to enable certain functionalities and improve performance.

      If you followed the prerequisite LAMP stack tutorial, several of these modules will have been installed along with the php package. However, it’s recommended that you also install these packages:

      • php-mbstring: a PHP extension used to manage non-ASCII strings and convert strings to different encodings
      • php-zip: a PHP module that supports uploading .zip files to phpMyAdmin
      • php-gd: another PHP module, this one enables support for the GD Graphics Library

      First, update your server’s package index if you’ve not done so recently:

      Then use apt to pull down the files and install them on your system:

      • sudo apt install php-mbstring php-zip php-gd

      Next, we can install phpMyAdmin. As of this writing, phpMyAdmin is not available from the default Debian repositories, so you will need to download the source code to your server from the phpMyAdmin site.

      In order to do that, navigate to the phpMyAdmin Downloads page, scroll down to the table with download links for the latest stable release, and copy the download link ending in tar.gz. This link points to an archive file known as a tarball that, when extracted, will create a number of files on your system. At the time of this writing, the latest release is version 4.9.0.1.

      Note: On this Downloads page, you will notice that there are download links labeled all-languages and english. The all-languages links will download a version of phpMyAdmin that will allow you to select one of 72 available languages, while the english links will only allow you to use phpMyAdmin in English.

      This guide will use the all-languages package to illustrate how to install phpMyAdmin, but if you plan to use phpMyAdmin in English, you can install the english package. Just be sure to replace the links and file names as necessary in the following commands.

      Replace the link in the following wget command with the download link you just copied, then press ENTER. This will run the command and download the tarball to your server:

      • wget https://files.phpmyadmin.net/phpMyAdmin/4.9.0.1/phpMyAdmin-4.9.0.1-all-languages.tar.gz

      Then extract the tarball:

      • tar xvf phpMyAdmin-4.9.0.1-all-languages.tar.gz

      This will create a number of new files and directories on your server under a parent directory named phpMyAdmin-4.9.0.1-all-languages.

      Then run the following command. This will move the phpMyAdmin-4.9.0.1-all-languages directory and all its subdirectories to the /usr/share/ directory, the location where phpMyAdmin expects to find its configuration files by default. It will also rename the directory in place to just phpmyadmin:

      • sudo mv phpMyAdmin-4.9.0.1-all-languages/ /usr/share/phpmyadmin

      With that, you've installed phpMyAdmin, but there are a number of configuration changes you must make in order to be able to access phpMyAdmin through a web browser.

      Step 2 — Configuring phpMyAdmin Manually

      When installing phpMyAdmin with a package manager, as one might in an Ubuntu environment, phpMyAdmin defaults to a "Zero Configuration" mode which performs several actions automatically to set up the program. Because we installed it from source in this guide, we will need to perform those steps manually.

      To begin, make a new directory where phpMyAdmin will store its temporary files:

      • sudo mkdir -p /var/lib/phpmyadmin/tmp

      Set www-data — the Linux user profile that web servers like Apache use by default for normal operations in Ubuntu and Debian systems — as the owner of this directory:

      • sudo chown -R www-data:www-data /var/lib/phpmyadmin

      The files you extracted previously include a sample configuration file that you can use as your base configuration file. Make a copy of this file, keeping it in the /usr/share/phpmyadmin directory, and rename it config.inc.php:

      • sudo cp /usr/share/phpmyadmin/config.sample.inc.php /usr/share/phpmyadmin/config.inc.php

      Open this file using your preferred text editor. Here, we'll use nano:

      • sudo nano /usr/share/phpmyadmin/config.inc.php

      phpMyAdmin uses the cookie authentication method by default, which allows you to log in to phpMyAdmin as any valid MariaDB user with the help of cookies. In this method, the MariaDB user password is stored and encrypted with the Advanced Encryption Standard (AES) algorithm in a temporary cookie.

      Historically, phpMyAdmin instead used the Blowfish cipher for this purpose, and this is still reflected in its configuration file. Scroll down to the line that begins with $cfg['blowfish_secret']. It will look like this:

      /usr/share/phpmyadmin/config.inc.php

      . . .
      $cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
      . . .
      

      In between the single quotes, enter a string of 32 random characters. This isn't a passphrase you need to remember, it will just be used internally by the AES algorithm:

      /usr/share/phpmyadmin/config.inc.php

      . . .
      $cfg['blowfish_secret'] = 'STRINGOFTHIRTYTWORANDOMCHARACTERS'; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
      . . .
      

      Note: If the passphrase you enter here is shorter than 32 characters in length, it will result in the encrypted cookies being less secure. Entering a string longer than 32 characters, though, won't cause any harm.

      To generate a truly random string of characters, you can install and use the pwgen program:

      By default, pwgen creates easily pronounceable, though less secure, passwords. However, by including the -s flag, as in the following command, you can create a completely random, difficult-to-memorize password. Note the final two arguments to this command: 32, which dictates how long the password string pwgen will generate should be; and 1 which tells pwgen how many strings it should generate:

      Next, scroll down to the comment reading /* User used to manipulate with storage */. This section includes some directives that define a MariaDB database user named pma which performs certain administrative tasks within phpMyAdmin. According to the official documentation, this special user account isn't necessary in cases where only one user will access phpMyAdmin, but it is recommended in multi-user scenarios.

      Uncomment the controluser and controlpass directives by removing the preceding slashes. Then update the controlpass directive to point to a secure password of your choosing. If you don't do this, the default password will remain in place and unknown users could easily gain access to your database through the phpMyAdmin interface.

      After making these changes, this section of the file will look like this:

      /usr/share/phpmyadmin/config.inc.php

      . . .
      /* User used to manipulate with storage */
      // $cfg['Servers'][$i]['controlhost'] = '';
      // $cfg['Servers'][$i]['controlport'] = '';
      $cfg['Servers'][$i]['controluser'] = 'pma';
      $cfg['Servers'][$i]['controlpass'] = 'password';
      . . .
      

      Below this section, you'll find another section preceded by a comment reading /* Storage database and tables */. This section includes a number of directives that define the phpMyAdmin configuration storage, a database and several tables used by the administrative pma database user. These tables enable a number of features in phpMyAdmin, including Bookmarks, comments, PDF generation, and more.

      Uncomment each line in this section by removing the slashes at the beginning of each line so it looks like this:

      /usr/share/phpmyadmin/config.inc.php

      . . .
      /* Storage database and tables */
      $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
      $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
      $cfg['Servers'][$i]['relation'] = 'pma__relation';
      $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
      $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
      $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
      $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
      $cfg['Servers'][$i]['history'] = 'pma__history';
      $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
      $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
      $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
      $cfg['Servers'][$i]['recent'] = 'pma__recent';
      $cfg['Servers'][$i]['favorite'] = 'pma__favorite';
      $cfg['Servers'][$i]['users'] = 'pma__users';
      $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
      $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
      $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
      $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
      $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
      $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';
      . . .
      

      These tables don't yet exist, but we will create them shortly.

      Lastly, scroll down to the bottom of the file and add the following line. This will configure phpMyAdmin to use the /var/lib/phpmyadmin/tmp directory you created earlier as its temporary directory. phpMyAdmin will use this temporary directory as a templates cache which allows for faster page loading:

      /usr/share/phpmyadmin/config.inc.php

      . . .
      $cfg['TempDir'] = '/var/lib/phpmyadmin/tmp';
      

      Save and close the file after adding this line. If you used nano, you can do so by pressing CTRL + X, Y, then ENTER.

      Next, you'll need to create the phpMyAdmin storage database and tables. When you installed phpMyAdmin in the previous step, it came with a file named create_tables.sql. This SQL file contains all the commands needed to create the configuration storage database and tables phpMyAdmin needs to function correctly.

      Run the following command to use the create_tables.sql file to create the configuration storage database and tables:

      • sudo mariadb < /usr/share/phpmyadmin/sql/create_tables.sql

      Following that, you'll need to create the administrative pma user. Open up the MariaDB prompt:

      From the prompt, run the following command to create the pma user and grant it the appropriate permissions. Be sure to change password to align with the password you defined in the config.inc.php file:

      • GRANT SELECT, INSERT, UPDATE, DELETE ON phpmyadmin.* TO 'pma'@'localhost' IDENTIFIED BY 'password';

      If haven't created one already, you should also create a regular MariaDB user for the purpose of managing databases through phpMyAdmin, as it’s recommended that you log in using another account than the pma user. You could create a user that has privileges to all tables within the database, as well as the power to add, change, and remove user privileges, with this command. Whatever privileges you assign to this user, be sure to give it a strong password as well:

      • GRANT ALL PRIVILEGES ON *.* TO 'sammy'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;

      Following that, exit the MariaDB shell:

      phpMyAdmin is now fully installed and configured on your server. However, your Apache server does not yet know how to serve the application. To resolve this, we will create an Apache configuration file for it.

      Step 3 — Configuring Apache to Serve phpMyAdmin

      When installing phpMyAdmin from the default repositories, the installation process creates an Apache configuration file automatically and places it in the /etc/apache2/conf-enabled/ directory. Because we installed phpMyAdmin from source, however, we will need to create and enable this file manually.

      Create a file named phpmyadmin.conf in the /etc/apache2/conf-available/ directory:

      • sudo nano /etc/apache2/conf-available/phpmyadmin.conf

      Then add the following content to the file

      /etc/apache2/conf-available/phpmyadmin.conf

      # phpMyAdmin default Apache configuration
      
      Alias /phpmyadmin /usr/share/phpmyadmin
      
      <Directory /usr/share/phpmyadmin>
          Options SymLinksIfOwnerMatch
          DirectoryIndex index.php
      
          <IfModule mod_php5.c>
              <IfModule mod_mime.c>
                  AddType application/x-httpd-php .php
              </IfModule>
              <FilesMatch ".+.php$">
                  SetHandler application/x-httpd-php
              </FilesMatch>
      
              php_value include_path .
              php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
              php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/:/usr/share/php/php-gettext/:/usr/share/php/php-php-gettext/:/usr/share/javascript/:/usr/share/php/tcpdf/:/usr/share/doc/phpmyadmin/:/usr/share/php/phpseclib/
              php_admin_value mbstring.func_overload 0
          </IfModule>
          <IfModule mod_php.c>
              <IfModule mod_mime.c>
                  AddType application/x-httpd-php .php
              </IfModule>
              <FilesMatch ".+.php$">
                  SetHandler application/x-httpd-php
              </FilesMatch>
      
              php_value include_path .
              php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
              php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/:/usr/share/php/php-gettext/:/usr/share/php/php-php-gettext/:/usr/share/javascript/:/usr/share/php/tcpdf/:/usr/share/doc/phpmyadmin/:/usr/share/php/phpseclib/
              php_admin_value mbstring.func_overload 0
          </IfModule>
      
      </Directory>
      
      # Authorize for setup
      <Directory /usr/share/phpmyadmin/setup>
          <IfModule mod_authz_core.c>
              <IfModule mod_authn_file.c>
                  AuthType Basic
                  AuthName "phpMyAdmin Setup"
                  AuthUserFile /etc/phpmyadmin/htpasswd.setup
              </IfModule>
              Require valid-user
          </IfModule>
      </Directory>
      
      # Disallow web access to directories that don't need it
      <Directory /usr/share/phpmyadmin/templates>
          Require all denied
      </Directory>
      <Directory /usr/share/phpmyadmin/libraries>
          Require all denied
      </Directory>
      <Directory /usr/share/phpmyadmin/setup/lib>
          Require all denied
      </Directory>
      

      This is the default phpMyAdmin Apache configuration file found on Ubuntu installations, though it will be adequate for a Debian setup as well.

      Save and close the file, then enable it by typing:

      • sudo a2enconf phpmyadmin.conf

      Then reload the apache2 service to put the configuration changes into effect:

      • sudo systemctl reload apache2

      Following that, you'll be able to access the phpMyAdmin login screen by navigating to the following URL in your web browser:

      https://your_domain/phpmyadmin
      

      You'll see the following login screen:

      phpMyAdmin login screen

      Log in to the interface with the MariaDB username and password you configured. After logging in, you'll see the user interface, which will look something like this:

      phpMyAdmin user interface

      Now that you’re able to connect and interact with phpMyAdmin, all that’s left to do is harden your system’s security to protect it from attackers.

      Step 4 — Securing Your phpMyAdmin Instance

      Because of its ubiquity, phpMyAdmin is a popular target for attackers, and you should take extra care to prevent unauthorized access. One of the easiest ways of doing this is to place a gateway in front of the entire application by using Apache's built-in .htaccess authentication and authorization functionalities.

      To do this, you must first enable the use of .htaccess file overrides by editing your Apache configuration file.

      Edit the linked file that has been placed in your Apache configuration directory:

      • sudo nano /etc/apache2/conf-available/phpmyadmin.conf

      Add an AllowOverride All directive within the <Directory /usr/share/phpmyadmin> section of the configuration file, like this:

      /etc/apache2/conf-available/phpmyadmin.conf

      <Directory /usr/share/phpmyadmin>
          Options FollowSymLinks
          DirectoryIndex index.php
          AllowOverride All
      
          <IfModule mod_php5.c>
          . . .
      

      When you have added this line, save and close the file.

      To implement the changes you made, restart Apache:

      • sudo systemctl restart apache2

      Now that you have enabled .htaccess use for your application, you need to create one to actually implement some security.

      In order for this to be successful, the file must be created within the application directory. You can create the necessary file and open it in your text editor with root privileges by typing:

      • sudo nano /usr/share/phpmyadmin/.htaccess

      Within this file, enter the following content:

      /usr/share/phpmyadmin/.htaccess

      AuthType Basic
      AuthName "Restricted Files"
      AuthUserFile /usr/share/phpmyadmin/.htpasswd
      Require valid-user
      

      Here is what each of these lines mean:

      • AuthType Basic: This line specifies the authentication type that you are implementing. This type will implement password authentication using a password file.
      • AuthName: This sets the message for the authentication dialog box. You should keep this generic so that unauthorized users won't gain any information about what is being protected.
      • AuthUserFile: This sets the location of the password file that will be used for authentication. This should be outside of the directories that are being served. We will create this file shortly.
      • Require valid-user: This specifies that only authenticated users should be given access to this resource. This is what actually stops unauthorized users from entering.

      When you are finished, save and close the file.

      The location that you selected for your password file was /usr/share/phpmyadmin/.htpasswd. You can now create this file and pass it an initial user with the htpasswd utility:

      • sudo htpasswd -c /usr/share/phpmyadmin/.htpasswd username

      You will be prompted to select and confirm a password for the user you are creating. Afterwards, the file is created with the hashed password that you entered.

      If you want to enter an additional user, you need to do so without the -c flag, like this:

      • sudo htpasswd /etc/phpmyadmin/.htpasswd additionaluser

      Now, when you access your phpMyAdmin subdirectory, you will be prompted for the additional account name and password that you just configured:

      https://your_domain_or_IP/phpmyadmin
      

      phpMyAdmin apache password

      After entering the Apache authentication, you'll be taken to the regular phpMyAdmin authentication page to enter your MariaDB credentials. This setup adds an additional layer of security, which is desirable since phpMyAdmin has suffered from vulnerabilities in the past.

      Conclusion

      You should now have phpMyAdmin configured and ready to use on your Debian 10 server. Using this interface, you can easily create databases, users, tables, etc., and perform the usual operations like deleting and modifying structures and data.



      Source link

      Build NGINX with PageSpeed From Source


      Updated by Linode

      Written by Linode

      Build NGINX with PageSpeed From Source

      What is Google PageSpeed?

      PageSpeed is a set of modules for NGINX and Apache which optimize and measure page performance of websites. Optimization is done by minifying static assets such as CSS and JavaScript, which decreases page load time. PageSpeed Insights is a tool that measures your site’s performance, and makes recommendations for further modifications based on the results.

      There are currently two ways to get PageSpeed and NGINX working together:

      • Compile NGINX with support for PageSpeed, then compile PageSpeed.
      • Compile PageSpeed as a dynamic module to use with NGINX, whether NGINX was installed from source or a binary.

        Note

        Installing NGINX from source requires several manual installation steps and will require manual maintenance when performing tasks like version upgrades. To install NGINX using a package manager see the NGINX section.

      This guide will show how to compile both NGINX and PageSpeed. If you would prefer to use PageSpeed as a module for NGINX, see this NGINX blog post for instructions.

      Before You Begin

      • You should not have a pre-existing installation of NGINX. If you do, back up the configuration files if you want to retain their information, and then purge NGINX.

      • You will need root access to the system, or a user account with sudo privileges.

      • Set your system’s hostname.

      • Update your system’s packages.

      Considerations for a Self-Compiled NGINX Installation

      Filesystem Locations: When you compile NGINX from source, the entire installation, including configuration files, is located at /usr/local/nginx/nginx/. This is in contrast to an installation from a package manager, which places its configuration files in /etc/nginx/.

      Built-in Modules: When you compile NGINX from source, no additional modules are included unless explicitly specified, which means that HTTPS is not supported by default. Below you can see the output of nginx -V using the PageSpeed automated install command on Ubuntu 16.04 with no additional modules or options specified.

        
      root@localhost:~# /usr/local/nginx/sbin/nginx -V
      nginx version: nginx/1.13.8
      built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5)
      configure arguments: --add-module=/root/incubator-pagespeed-ngx-latest-stable
      
      

      Contrast this output with the same command run on the same Ubuntu system but with the binary installed from NGINX’s repository:

        
      root@localhost:~# nginx -V
      nginx version: nginx/1.13.8
      built by gcc 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
      built with OpenSSL 1.0.1f 6 Jan 2014 (running with OpenSSL 1.0.2g  1 Mar 2016)
      TLS SNI support enabled
      configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
      
      

      Build NGINX and PageSpeed

      The official PageSpeed documentation provides a bash script to automate the installation process.

      Note

      The automated installation script will install several compilation tools needed to install PageSpeed. If you are using a production environment, ensure you uninstall any packages that are no longer needed after the installation has completed.

      1. If you plan to serve your website using TLS, install the SSL libraries needed to compile the HTTPS module for NGINX:

        CentOS/Fedora

        yum install openssl-devel
        

        Ubuntu/Debian

        apt install libssl-dev
        
      2. Run the Automated Install bash command to start the installation:

        bash <(curl -f -L -sS https://ngxpagespeed.com/install) 
        --nginx-version latest
        
      3. During the build process, you’ll be asked if you want to build NGINX with any additional modules. The PageSpeed module is already included, so you don’t need to add it here.

        The options below are a recommended starting point; you can also add more specialized options for your particular use case. These options retain the directory paths, user and group names of pre-built NGINX binaries, and enable the SSL and HTTP/2 modules for HTTPS connections:

        --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module
        
      4. Next you’ll be asked if you want to build NGINX. You’ll be shown the destination directories for logs, configuration files and binaries. If these look correct, answer Y to continue.

          
        Configuration summary
          + using system PCRE library
          + using OpenSSL library: /usr/bin/openssl
          + using system zlib library
        
          nginx path prefix: "/etc/nginx"
          nginx binary file: "/usr/sbin/nginx"
          nginx modules path: "/usr/lib/nginx/modules"
          nginx configuration prefix: "/etc/nginx"
          nginx configuration file: "/etc/nginx/nginx.conf"
          nginx pid file: "/var/run/nginx.pid"
          nginx error log file: "/var/log/nginx/error.log"
          nginx http access log file: "/var/log/nginx/access.log"
          nginx http client request body temporary files: "/var/cache/nginx/client_temp"
          nginx http proxy temporary files: "/var/cache/nginx/proxy_temp"
          nginx http fastcgi temporary files: "/var/cache/nginx/fastcgi_temp"
          nginx http uwsgi temporary files: "/var/cache/nginx/uwsgi_temp"
          nginx http scgi temporary files: "/var/cache/nginx/scgi_temp"
        
        Build nginx? [Y/n]
        
        
      5. If the build was successful, you’ll see the following message:

          
        Nginx installed with ngx_pagespeed support compiled-in.
        
        If this is a new installation you probably need an init script to
        manage starting and stopping the nginx service.  See:
          http://wiki.nginx.org/InitScripts
        
        You'll also need to configure ngx_pagespeed if you haven't yet:
          https://developers.google.com/speed/pagespeed/module/configuration
        
        
      6. When you want to update NGINX, back up your configuration files and repeat steps two through four above to build with the new source version.

      Control NGINX

      NGINX can be controlled either by creating a systemd service or by calling the binary directly. Choose one of these methods and do not mix them. If you start NGINX using the binary commands, for example, systemd will not be aware of the process and will try to start another NGINX instance if you run systemctl start nginx, which will fail.

      systemd

      1. In a text editor, create /lib/systemd/system/nginx.service and add the following unit file from the NGINX wiki:

        /lib/systemd/system/nginx.service
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        
        [Unit]
        Description=The NGINX HTTP and reverse proxy server
        After=syslog.target network.target remote-fs.target nss-lookup.target
        
        [Service]
        Type=forking
        PIDFile=/run/nginx.pid
        ExecStartPre=/usr/sbin/nginx -t
        ExecStart=/usr/sbin/nginx
        ExecReload=/bin/kill -s HUP $MAINPID
        ExecStop=/bin/kill -s QUIT $MAINPID
        PrivateTmp=true
        
        [Install]
        WantedBy=multi-user.target
      2. Enable NGINX to start on boot and start the server:

        systemctl enable nginx
        systemctl start nginx
        
      3. NGINX can now be controlled as with any other systemd-controlled process:

        systemctl stop nginx
        systemctl restart nginx
        systemctl status nginx
        

      NGINX binary

      You can use NGINX’s binary to control the process directly without making a startup file for your init system.

      1. Start NGINX:

        /usr/sbin/nginx
        
      2. Reload the configuration:

        /usr/sbin/nginx -s reload
        
      3. Stop NGINX:

        /usr/sbin/nginx -s stop
        

      Configuration

      NGINX

      1. Since the compiled options specified above are different than the source’s defaults, some additional configuration is necessary. Replace example.com in the following commands with your Linode’s public IP address or domain name:

        useradd --no-create-home nginx
        mkdir -p /var/cache/nginx/client_temp
        mkdir /etc/nginx/conf.d/
        mkdir /var/www/example.com
        chown nginx:nginx /var/www/example.com
        mv /etc/nginx/nginx.conf.default /etc/nginx/nginx.conf.backup-default
        
      2. In NGINX terminology, a Server Block equates to a website (similar to the Virtual Host in Apache terminology). Each NGINX site’s configuration should be in its own file with the name formatted as example.com.conf, located at /etc/nginx/conf.d/.

        If you followed this guide or our Getting Started with NGINX series, then your site’s configuration will be in a server block in a file stored in /etc/nginx/conf.d/. If you do not have this setup, then you likely have the server block directly in /etc/nginx/nginx.conf. See Server Block Examples in the NGINX docs for more info.

        Create a configuration file for your site with a basic server block inside:

        /etc/nginx/conf.d/example.com.conf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        server {
            listen       80;
            listen       [::]:80;
            server_name  example.com www.example.com;
            access_log   logs/example.access.log main;
            error_log    logs/example.error error;
        
            root         /var/www/example.com/;
        
        }
      3. Start NGINX:

        systemd:

        systemctl start nginx
        

        Other init systems:

        /usr/sbin/nginx
        
      4. Verify NGINX is working by going to your site’s domain or IP address in a web browser. You should see the NGINX welcome page:

        NGINX welcome page

      PageSpeed

      1. Create PageSpeed’s cache location and change its ownership to the nginx user and group:

        mkdir /var/cache/ngx_pagespeed/
        chown nginx:nginx /var/cache/ngx_pagespeed/
        
      2. Add the PageSpeed directives to your site configuration’s server block as shown below.

        /etc/nginx/conf.d/example.com.conf
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        
        server {
        
              ...
        
            pagespeed on;
            pagespeed FileCachePath "/var/cache/ngx_pagespeed/";
            pagespeed RewriteLevel OptimizeForBandwidth;
        
            location ~ ".pagespeed.([a-z].)?[a-z]{2}.[^.]{10}.[^.]+" {
                add_header "" "";
                }
        
            location ~ "^/pagespeed_static/" { }
            location ~ "^/ngx_pagespeed_beacon$" { }
        
            }

        Note

        RewriteLevel OptimizeForBandwidth is a safer choice than the default CoreFilters rewrite level.
      3. NGINX supports HTTPS by default, so if your site already is set up with a TLS certificate, add the two directives below to your site’s server block, pointing to the correct location depending on your system.

        pagespeed SslCertDirectory directory;
        pagespeed SslCertFile file;
        
      4. Reload your configuration:

        /usr/sbin/nginx/ -s reload
        
      5. Test PageSpeed is running and NGINX is successfully serving pages. Substitute example.com in the cURL command with your Linode’s domain name or IP address.

        curl -I -X GET example.com
        

        The output should be similar to below. If the response contains an HTTP 200 response and X-Page-Speed is listed in the header with the PageSpeed version number, everything is working correctly.

          
        HTTP/1.1 200 OK
        Server: nginx/1.13.8
        Content-Type: text/html
        Transfer-Encoding: chunked
        Connection: keep-alive
        Vary: Accept-Encoding
        Date: Tue, 23 Jan 2018 16:50:23 GMT
        X-Page-Speed: 1.12.34.3-0
        Cache-Control: max-age=0, no-cache
        
        
      6. Use PageSpeed Insights to test your site for additional improvement areas.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link